Questionable scheme to appease managers and clients

Ok, so the other day, prompted by concerns raised in a thread about getting to write Elm at work, I had this kind of hilarious idea of making an elm-to-react compiler. This warrants some elaboration: I definitely recognize the fear managers/clients sometimes have of using Elm, because React is the more mainstream choice. So I thought: what if you could just exit Elm anytime? So you tried Elm, but you ran into a blocker- no problem, with a click of a button, you have a React app!

Redux is already inspired by Elm, so I think it would map over pretty easily. It could just be a really cleanly functional React app, which is already broadly considered good practice, right? It could even be in Type Script to keep the types intact! Is this crazy? Would anyone be up for collaborating? Also, what even is the recommended way of writing React apps now a days? Do people still use Redux? What’s even is Preact?! So much research to do! Anyways, would love to hear your thoughts!

11 Likes

I agree that it would be appeasing to have the barrier to exit really low (though if it’s too low, people won’t want to stick with Elm enough? :cry: ). I’m thinking that TypeScript and Flow’s ability to be easily removed from JavaScript code probably helped their adoption (even though Flow kind of died now).

I have had the “pleasure” of porting an Elm app to a React app when leaving my previous job. You’re right that it maps relatively easily, at least much easier than React to Elm.

Surprisingly, I found that Redux wasn’t as smooth a mapping as I thought it would be. Redux handles the state, and should be made of pure functions to work well. That seems to be a perfect fit for Elm’s update, but the difference is in how the commands or side-effects are handled.

In React/Redux, code that triggers a “Msg” only sends a message to Redux to update the state. If there is a need to trigger a command like an HTTP request, I found that a more suitable place was to have that triggered next to the call to the Redux update function, because that should not be done in the “reducer”.

There are a few side-effects to that:

  1. Commands are not as centralized as in Elm
  2. Redux messages say “how the state should change”, rather than “what happened”. “IncrementCounter” vs “IncrementButtonWasClicked” if you will.

Because of these differences (and probably more that I forgot), it’s not a one-to-one mapping and the result doesn’t feel as nice as Elm’s.

I also remember that React requires you to set keys to HTML elements when you’re mapping over children and can get into weird states if you don’t (and get warnings in your console), whereas Elm only makes that an optional optimization.

what even is the recommended way of writing React apps now a days? Do people still use Redux?

I don’t know, I haven’t followed React enough since working with Elm full-time. That said, there are plenty of flavors for Redux out there to deal with the results of, like redux-thunk or redux-saga, so even if React/Redux is the compilation target, there would be a need to choose one flavor, or to go with a custom one.

I don’t know whether I would advise you to target Redux or instead advise not to use Redux and instead replicate Elm’s TEA, which is relatively simple anyway, for the reasons I listed above. I feel like that would be a bit simpler.

Also, I don’t know what will then be easiest, between prettifying the output of the compiler and transforming it to use React, or taking Elm source code and “compiling” it to JavaScript. Doing the former would make it trivial to migrate Elm packages code (instead of having to download it). If converting from the Elm source code, I would definitely recommend writing it to TypeScript. My project rewrite targeted JavaScript (the team didn’t buy in TypeScript), and even though I feel like I did a great job, I can assure the quality of the project declined compared to when it was Elm.

I think I’ll keep working on my own projects (much to do with elm-review), but I have to admit I had an unexpected amount of fun when rewriting the Elm project to React (though that may have been because I like doing this architectural kind of work and I also tried doing it gradually so that we could still ship things to production). So if this sounds interesting to someone, go for it!

2 Likes

I really like this idea - basically an “eject” command similar to what is available on create-react-app.

react/redux is still a really common setup that lots of people use and like. Using redux-toolkit (currently recommended) also gives a good direction on how to deal with side effects and structure sub-TEAs.

I have no idea how complex would it be to actually compile everything to JS (composition, variants, etc, etc.) but since Elm is such a small and clearly defined language, I would guess it wouldn’t be impossible for someone used to these things?

1 Like

Just want to second @georgesboris here, as well as say that some groups within the React community advise against Redux in favor of hooks. Additionally there are some who promote Xstate. I think “ejecting” to Redux would be a very safe choice though as it can then be up to the team to decide if they want to migrate to another state management tool.

My one concern would be for the team behind this tool in keeping up with React. While React doesn’t move as fast as Angular, Vue, or Ember it does move faster than Elm. There would probably be a lot of time spent on just keeping it up to date with idiomatic React. Just thinking about the various changes around functional vs class vs functional + hooks components and even within those there are different idiomatic changes over the years.

Definitely don’t want to discourage you though.

3 Likes

Ha I also had this dubious “pleasure” of porting an Elm app to React. @jfmengels is exactly right w.r.t. to the non-obvious mapping of effect handling. The other thing I found was that curiously React-Redux is missing the concept of a dedicated init function. I suppose you are meant to initialise your redux store and then just call your side effects bare… shivers.

Re keeping pace with React: you could always run codemods on the output, so that shouldn’t be that tricky.

One problem I foresee is what to do about the libraries? If I use elm-ui in my project, what is my React code going to look like? I suppose one could simply try to compile all dependencies into a vendor directory, but to make exiting smoother it would be nicer to support some way to “map” elm libraries to idiomatic JS libraries.

Also, there is some precedent for this.

2 Likes

redux-loop handles side-effects like Elm, by changing the signature of the update function, and working with Cmds. I’ve used it in two major work projects with React and Redux, and it’s a pleasure to work with (vs. redux-saga and especially redux-thunk, which I don’t approve of).

4 Likes

You could make the argument, if you need to get off Elm, just eject to Javascript? The problem with that is that the generated code does not look like hand written code, so you would be trying to maintain something that is not really maintainable by hand with that approach.

Or maybe eject from C++ and maintain the assembler code by hand?

If Elm compiled to React, that would be the case too. Unless you tried very hard to generate idiomatic React code that is maintainable after the eject decision?

I have prior related experience of doing this in the Java world. I wrote a model driven code generator that creates idiomatic Spring Boot projects - project structured into database, business logic and web API layers, everything named with sensible names (taken from the model), unit tests, build scripts, even comments all generated. The whole approach was designed around the idea of using codegen to quickly get a project 80% done, then gradually turn off code generation for various parts of the project as you ejected into Java land. It is possible to do this, but its a lot harder than not doing it. Also this project only really generated the skeleton CRUD API of a project - business logic was always added by hand, so the way it operated was quite different to a conventional compiler.

You did say this is a questionable scheme…

1 Like

On the other hand, if the tool is quite high quality, it could work as a more pleasant way to write React apps for clients. Just write it in Elm, than compile to React and ship it :smiley:

8 Likes

True, there could be a business opportunity here :slight_smile:

This could be a great idea! For trying to write (from scratch) some Angular code following “Elm principles”, I encounter some caveats in TypeScript. The most painful was the difficulty to have an “opaque type” simply wraping a primitive type with a “smart constructor” (e.g. type Url, fromString : String -> Maybe Url). For now the best thing I found is creating a A class for this opaque type with a private constructor and a static method which returns A | null.

About state management, I read this article: Write dumb code which advocates for “props drilling” in react, so no store neede. “Props drilling” is, if I understand well, the way we do in elm so translate to this pattern should be easy doable.

However, we don’t have the concept of “component” at all, so I don’t think we could be able to translate to idiomatic React code without this notion (but I’ve written React at all, so maybe it is fine to not create components).

1 Like

I think you can create a rough equivalence between what React calls components and in Elm with named functions. So in React a component could be:

const Button = ({label, onClick}) => 
    <button class="my-button" onClick={onClick}>{label}</button>

which would correspond to:

button : String -> msg -> Html msg
button label onClick =
    Html.button 
       [ Html.Events.onClick onClick, Html.Attributes.class "my-button" ]
       [ Html.text label ]

Of course a practical compiler might struggle with some of the details. Like components in React always take a single object argument, whereas positional arguments are more common in Elm view functions. So the compiler would need some heuristics on some sensible naming of these attributes.

Of course components in React do have slightly different semantics than the VDom based functions in Elm do. But for most apps, as long as you stick to hookless function components, it is a very close mapping.

There is also Hyperapp which I believe maps TEA quite closely. In case React doesn’t fit, maybe it would be easier to generate a Hyperapp application?

2 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.