Why Elm? [pdf]
oreilly.com
oreilly.com
The Elm Architecture makes it very easy to create small type safe components, but it forces parent components to manage the state + structure all children components. This makes it hard to use a component and to separate concerns.
In order to create a list of stateful children, you must:
1. Add a list of the child states to the component state, event if the parent component doesn't care about the child's state.
2. Handle adding + removing child state's manually
3. Map all scoped child state updates through your reducer, adding a constructor to your action type
4. Finally, map each of the child state's to the component's view.
You end up big types and match expressions that just manipulate lists. I have yet to see a FRP library that allows you to just use a component without introducing boilerplate.
This is trivially debunked: here is a 4,000 LoC open-source Elm SPA with small, type-safe reusable views that have state and don't do all this. :P
https://dev.to/rtfeldman/tour-of-an-open-source-elm-spa
Plenty of counterexamples in there.
Here's one. https://github.com/rtfeldman/elm-spa-example/blob/master/src...
Here's another. https://github.com/rtfeldman/elm-spa-example/blob/master/src...
Here's yet another. https://github.com/rtfeldman/elm-spa-example/blob/master/src...
Check this out: https://github.com/rtfeldman/elm-spa-example/blob/master/src...
Go to the bottom, see how it manually maps the child events of the feed update? You have to duplicate that code for every single child component. All the other steps I described are there also.
My top 3 complaints:
1) The crazy boilerplate, the Elm architecture forces you to store all your app state in a single atom. In my experience, this doesn't scale well and is bloody annoying (see, even the redux author say you should never store everything in the single atom/store); Also, when should state be cleaned up? When the component is shown again, but then you have stale state in the meantime; or when the component is removed? before or after it's being transitioned out? (you don't want to see a flash of empty data)
2) The snail pace at which Elm evolves. I will have delivered 3 or 4 apps for my clients by the time the single Elm maintainer added 5% of the WEB API as native Elm modules. By the time he's done, new WEB APIs will have to be covered. It's never going to end. That means, a complex Elm app always has javascript. In that case, I'd rather just use typescript and nothing else.
3) The religious focus on purity. The elm maintainer is an haskell user, and it shows. Can you believe that to read the current time or generate a random number, you must be within a component's update function, ask for one or the other and you will get your result later, asynchronously? I know some people love that their entire program are fully pure in a mathematical sense, but this always struck me as having way more cons than pros.
Also, there is always "just one way to do this or that, all the other ways are disapproved by the Elm creator" The environment/community is so closed! If the Elm maintainer did anticipate your very specific need (low chance, he can't do everything by himself), the answer is: "always use that, you should never use this other thing, it's very dirty", if not, "just use this JS hack instead". This is good for beginners seeking very strong guidance, not good for power users trying to deliver stuff fast.
You can tell Elm is still a beta product; it has lots of cool stuff, but even more rough edges.
Otherwise the language is kinda nice and simple (some would say too simple, I don't know If I agree; maybe) and is great for teaching FP to first timers.
You say this like generating a random number is a small deal. In a sense this is very true! From the machine's perspective generating a random number is a quick operation. However, from the perspective of maintainers who come after you losing determinism is no small thing, and IMHO deserves to be marked clearly in the type system.
2) In my experience, with typescript/javascript you instead get to deal with the fact that in the same period of time the API for webpack has changed, the api for react-router has changed twice, etc. Slow is not necessarily bad, especially considering the reason things are slow is because of the focus on doing things right. Also, just because you can't escape a tiny amount of javascript, having the rest of the application in Elm isn't worth it? How strange.
3) I admit that it sometimes is a bit annoying that you can't get things like the current time --right now--, and instead have to thread that through the update chain. But overall I find this to be a good thing. It's great when learning new code bases to easily identify where side-effects can happen just by looking at the type signature.
> Also, there is always "just one way to do this or that...
As a power user, this is the best thing about Elm. The fact that javascript isn't like this, is why I never want to maintain another javascript project that I didn't personally create, ever again.
> You can tell Elm is still a beta product;
Elm is a alpha product, so this is really a great compliment.
And no, I wouldn't want to maintain a codebase written in a dynamic language ever again too :p
> There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.
> Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it.
I had those tweets in mind:
https://twitter.com/dan_abramov/status/725089775783391232?la...
https://twitter.com/dan_abramov/status/759383530120110080?la...
plus many other tweets.
And that answer: https://github.com/reactjs/redux/issues/1287
dyn :: MonadWidget t m => Dynamic t (m a) -> m (Event t a)
The types take a bit of getting used to (reflex-frp is not nearly as beginner friendly as Elm), but once you are used to them it's incredibly expressive. Being able to just wrap a changing widget into a normal widget without needing to change the rest of your code is a big part of why I ultimately like reflex's model far more than the Elm Architecture.I'm the author of https://github.com/calmm-js.
I wouldn't necessarily describe it as (pure) FRP, but the combination of techniques used in Calmm pretty much eliminates all boilerplate: in the limit, a truly useful component can be just a single function with a body of a single line of code. Using that component then is just a matter of instantiating the component with desired parameters (no boilerplate). The lack of boilerplate makes it practical to eliminate even small scale repetition with local components.
Parameters to components in Calmm can represent read-only (Observable) or read-write (Atom :> Observable) state. RW state can be sliced with lenses and be used as RO state without glue. RO state can be arbitrarily combined to compute desired properties. Thanks to being able to effortlessly slice RW state, components can be easily made independent of the application state as a whole (IOW, a component can be made to take only just the state it needs as parameters) and be made truly reusable.
Calmm also makes it simple and easy to create both stateless components (consider #1) and components that can optionally use local state so that the user of the component can actually choose whether the component uses local state or a slice of global state.
Calmm also primarily guides you to use stateless, time independent, continuous, observable properties (combine) rather than streams of discrete events (merge + scan) (see #2) which actually introduce undesirable local state and force you to consider timing dependencies again.
#1) https://awelonblue.wordpress.com/2012/10/21/local-state-is-p... #2) https://awelonblue.wordpress.com/2012/07/01/why-not-events/
Personally I think that the entire top-down state tree is a bad idea, but I'm willing to be convinced. I'd much prefer a modeling like the actor approach.
Applications developed this way are very easy to change and update. Deleting a completely self contained component, not having to update the parent by removing the wiring of the data, is a blessing.
Rather than passing parts of the model to the children (as you'd do with React props), you pass lenses into said parts.
It enables child components to read/write to the global store without knowing its structure. As long as it receives an appropriate (set of) lens(es) it is happy.
So, for example, if you have a list of strings, rather than passing the nth string to the nth component, you'd pass a lens to the nth string. The component can then not only read but also write to its slot.
Lenses can also be composed, so you can create them on the go as you'd pass sub-props to grand-children.
https://github.com/calmm-js/partial.lenses <= a comprehensive lens library
http://ramdajs.com/ also has lenses.
These libs are strongly influenced by the FP world. The underlying data structures are immutable, writing to a lens creates a new version of the store (as efficiently as possible, by recycling intact objects).
However, recently I discovered Halogen [0]. It is very similar to Elm Architecture, however, every component has its own _internal_ state, so you do not have to thread all state changes to the top-level component. It does this by existentially quantifying over the State [1].
Halogen is written in Purescript, which is a transpiler for javascript (yes transpiler, not compiler). Which means you basically get javascript semantics with a haskell typesystem + very good interop with JS, and no runtime system. The code purescript generates is actually readable. [2]
[0] https://github.com/slamdata/purescript-halogen/tree/master/d... [1] https://wiki.haskell.org/Existential_type [2] https://purescript.org/ [3] http://try.purescript.org
Transpilers are a subset of compilers; it is impossible for "yes transpiler, not compiler" to be true.
The idea is that you declare read/write optics than you then compose and reuse.
But on a web app, say, in how many places can you update a person's address?
Writing non generic code that update it in a non generic way (throw away code) is much less ceremony than defining an optic and only using it once.
Do you know of any good resources on that? Or projects using these concepts? Sounds interesting.
Given all the benefits of Elm, 6 lines of boiler plate (one line added in model, 5 lines added in update) is a small price to pay. Especially considering the small amount of components (in contrast to React, not everything is a component).
Many benefits over using js, and basically no downsides apart from the initial learning curve. And even that could be equal to the learning you have to do every time your team decides to add/use a new foo.js library or framework to the project.
With Elm:
- you are confident about your code
- others ( and your future self ) can actually read it and understand it
- it pushes you to think before you write
- refactoring is a breeze, not a nightmare
I hope I won't have to use js on my next gig.edit: Also, I cannot speak highly enough about how helpful the compiler is, and the community in the slack channel. Makes the whole experience a joy.
Love how structured things are and even though Elm is an "in progress" kind of thing - still no left-pad or js fatigue type issues (unless you really try to make them happen).
I always anticipated that would be a downside of using Elm. I know there must be hooks to let you integrate pure javascript (jQuery-type) libraries and widgets into your app, but wouldn't it be really annoying?
I'm writing Angular apps these days and one of the most time-consuming tasks is this sort of integration (for instance, right now I'm trying to add FullCalendar [0] to an app), and it doesn't work so nicely with the digest cycle of Angular, or play nicely with how data is passed around. And Elm is even more alien to javascript than a javascript app using Angular would be to javascript. So how is this area for you?
I guess my approach is different. I only use a library when I have to, meaning that it's of "high quality" and it will save lots of time. Otherwise, I'd rather implement the feature myself, so I know the code and I can extend it later.
In that sense, Elm covers me completely. There are many small good utility libraries to use, but I only use 2-3 of them. And most of them, you can read the code, quickly understand what it does and pick the code you want to use in your app.
https://greim.github.io/elmsweeper/
https://greim.github.io/electron-golf/
Honestly I think it would have been easier to build the first iterations of these in JS (React or something) but with the second->nth iterations, it became easier with Elm.
A lot of it might be that simple games are fun and relatively easy, so well suited for learning new stuff in your spare time.
Then again, the electron golf game re-renders SVG DOM at 60fps. Building web apps that way hadn't occurred to me until React came along, and I didn't attempt it until Elm.
There's a lot of snake games, tic-tac-toes and 2048 clones -- I was looking for more along the lines of multiple page apps where elm made it a lot easier to build, and stories thereof.
But, I also tried to learn and use OCaml and Haskell in actual projects. They are nowhere near as friendly and approachable as Elm to beginners.
Elm definitely helped me understand many concepts in OCaml and Haskell. But to go beyond that, it's difficult.
So it's really good at what it does, but for everything else, it's just incomplete. For instance, Elm seems like a natural fit for an offline-first ServiceWorker design. But I don't think it really covers much of the just-outside-of-core-Javascript browser APIs. You're likely to run into "I have some really interesting ideas about how to cover that a super elegant way, but I'm concentrating on rewriting the $WHATEVER right now" newsgroup posts, but that's it… :-(
I've been watching Elm for some time. I'll wait until it's done. So my reason for not using Elm is: I'd rather be patient.
For everyone who need to write SPAs right now (or in 2 years) it's just meh.
That's the secret though, right? When you have teams of dozens of developers in a company of thousands the cost of adoption and the resistance you encounter increases exponentially. At least I can evangelize React/Redux/Typescript and get a sane development experience that's still just JavaScript.
There are a few issues you have to deal with. The first is how adoption happens. This is generally based on power structures and reputation. If you are in a position to mandate a change, the cost is potentially lower (see below, though), but if you are not then you have to influence enough people to make a change.
There are many ways to do this, but I will split it into 2 for simplicity. First, you can try to influence the rest of the group directly. As the group size grows, this becomes progressively more difficult because your message is lost in the noise of other people pressing their own agendas. Once you get above certain point (say about 8-10 people), your odds of success drops to near zero.
The other main way is to pilot a project and use its success to influence others. Setting up the pilot requires influencing a smaller group and therefore you have higher chances of success. The problem is that when you pilot a new way of doing things, you are intentionally breaking consensus. You now have a small group of individuals who have experience that nobody else in the group has. Whether the adoption of the new technique is successful or not, this leads to increased cost as attitudes change.
The bigger the group, growing out a pilot into a larger project becomes more expensive. This is essentially the same problem as before -- you may be in a position now to mandate a way of working, but if you are not, then you have to create another round of pilots to move the idea around the group. But now you have another problem -- the implementors of the new pilots are different than the implementors of the original pilot. So you will now have more than one message coming out. You now have to harmonise that message in order to drive adoption.
At every step, there is a chance of failure (sometimes quite high). The chance of failure grows as size of the group increases, because the external pressures to not change are higher. Again, there are several possible outcomes, but I will pick 2 for simplicity. You may find that your idea is rejected. You have now spent resources on your pilot that you will have to reimplement in another way. Another outcome is that your adoption is simply stalled -- you split your group permanently. This has cost implications ranging from staffing, to training, to tooling, to management, etc.
Now, assuming you don't fail, you may have organically grown the adoption in your group (which is ideal), but as your group size grows, the possibility of that decreases (because the risk of failure increases based on the size of the group). More likely is that you have gotten enough reputation to be able to mandate a change.
Mandating changes has costs as well. The first is that some people will be uncomfortable with the change and will quit. You now have to replace those individuals - which costs you both lost productivity, referral fees, and training costs. This is more likely as the group size increases because you are more likely to have diverse, strongly held opinions.
The next biggest cost would appear to be training, retooling, etc. However, there is a hidden cost -- compliance. Very good people who dislike the change will leave. Very poor people will not leave -- they will simply refuse to follow the new strategy competently. This may be because they are depressed over the change, scared of how the change affects their influence, and many other things. These people can often end up sabotaging your projects (mostly unintentionally, but as they are usually quite upset they also don't care if they do). As the size of the group increases, the chances of building an unshiftable group of discontents grows.
If you want to make a lot of money, become a consultant introducing new development techniques into large organisations like banks. It's a bit like siphoning all their operating capital into your pocket because you have no hope of success and can blame all your failure on other people.
For those who are new to any kind of development, learning Elm would be no more different/difficult than learning react redux.
I've made the decision to use elm and put my two frontend devs to learn it. They managed to start contributing to the app in less than a week.
So if you have the chance, I think it's worth a shot. I thought I'd never learn elm, but so far my experience proved that it's much easier than it seems.
Refactoring Elm code isn't as painful for me as Javascript or Ruby - because it's statically compiled, it gets rid of a whole class of errors. The compiler does an excellent job at where compilation fails and suggestions to fix the error.
Additionally, I find in Elm (and other ML-typed languages) that it's easy to model states of your app (e.g. like a state machine) and events that change the state of your app. Adding/modifying states and events in Elm is one of its strong suits and it really forces you to logically think through how your app transitions from one state to another. Scott Wlaschin has an excellent talk on Domain Driven Design in F# that articulates this point quite well.
I think the basic gist was that it's type-system is far from complete.
Not that I say type classes (or generics) are useless, but keeping a language simple (in the sense of keeping the feature list small) has benefits on its own. I did not to complicated projects in Elm, but I watch the language closely and really like what I have seen so far, so I hope Evan keeps up the good work, as I think he has pretty good taste for language design.
https://github.com/elm-lang/core/pull/739
Basically, if you have to deal with local time, this isn't the language for you. If you expect to be able to add to the platform where you find it lacking, this isn't the language for you.
At this point, Typescript has the momentum to make it irrelevant
However I have to agree with you that the lack of support for local time is pretty unfortunate.
Elm's error messages and semantic versioning are still a class above anything else, though. Sad to see it rotting on the vine like this.
It is impossible that all of them will be popular, plus more general purpose, doesnt really seem to help with popularity ... look at sql or r ... very popular, and very domain specific
Elm tries to protect new users: "here, use andThen to chain two computations"
Purescript: React bindings have literal mentions of monads and functors... I think they're useful as concepts; but I don't want to think about it everytime I write a component's render function.
1) building & growing the app - there's only way to grow and it must follow the same conventions as every other Elm app 2) bringing on developers - their main hurdle is the business logic itself and not the structure of the app 3) finding resources in the community - most everybody is traveling down the same road and can teach one another the lessons they've learned
However, if they could just be convinced to learn Elm I'm positive that Elm would be much easier to maintain and improve than just React by its self. React can definitely be maintained easily, but Elm does a lot of the work to check itself and also enforces discipline that React can't (although you could get a lot by just using Flow extensively and treating its errors as blockers).
Using a component isn't simply matter of importing and using, you have to integrate its state into your component, send all it's events back to its reducer, and then map each child state to the corresponding view. You end up with lots of boilerplate.
Let's say a create a stateless subscribe button component, that's part a of a stateless header component. It just accepts a listener that is called when the button is clicked.
Now, my designer wants me to add a dropdown menu to the subscribe button that allows people to enable email notifications. The dropdown menu needs state, so now I add a reducer and model to my subscribe button, I need to wire that up through the stateless header component making it stateful, then finally wire it into the the atom for the app. I ended up doing these chains of updates and adding parameters to add simple features.
Also, elm lacks typeclasses. Try making a modular form builder with elm that allows other authors to define fields with custom state, it's a mess.
local state would invalidate many things in the Elm ecosystem like the time travel debugger, all the tutorials, etc. It's not such an easy problem, you have to write a new reconciler that only renders the things that have changed instead of always rendering the entire tree.
btw, redux only deal with global state but encourages the use of local (setState based) state whenever it makes sense.
(The Scala.js compiler itself does not run in the browser, though.)
I currently use Rails and this helper[1] to render an Elm component. Then I mount it like this: [2]
``` <%= elm_component('TeamPicker', {flags: { formAuthenticityToken: form_authenticity_token, domain: current.domain, teams: user.my_teams.map { |team| {id: team.id.to_s, name: team.name || ""} }, current_team: {id: current.team.id.to_s, name: current.team.name || ""} }}, {tag: 'div', class: "nav-item"}) %> ```
I think this is an easy way to get into Elm world step by step.
My site is still normal HTML but I used ELM here and there for some component require interaction/multi step form etc.
---
[1]: https://github.com/NotyIm/trinity/blob/master/app/helpers/el... [2]: https://github.com/NotyIm/trinity/blob/master/app/views/part...
At the moment there's only an internal API for the latter, and it's pretty ugly. I think what would be good for Elm would be to spin up a few separate Elm processes and provide an easy interface to make them communicate, sort of like in Erlang. You can do this using ports and binding them together with js right now, but it's kind of ugly.
[0] https://news.ycombinator.com/item?id=14299816 [1] https://learningmusic.ableton.com/
Oh, how tech names keep getting recycled!!!
(And even on the front-end there's `bucklescript-tea`; a port of the Elm architecture to OCaml)
I think Elm is to address the issue that JS is the only thing that runs in a browser. With that said, I suppose you could use it with Node as well.
Some people do it, but it's a rough ride that need you to understand Elm's internals.
As for a "useful SPA" - a useful SPA has at least 300 gal. capacity and seating for 4+.
Sigh - arbitrarily re-using names for pre-existing widgets is silly. Sort like claiming that Kleenex is actually a machine tool.
Time.every (second / 2) Tick
and to still move it the same amount: Tick newTime ->
(newTime * 2, Cmd.none)I'd be really surprised if it were.
Awesome.