How do you cope with basically not having deeply nested components?
- The system has 150+ users that was migrated over night and has been online for about 2 years now.
- No runtime exceptions in production. In testing the only thing we had was "Stack size exceeded" in Internet Explorer due to a poorly written time zone module.
- Translation was solved with one build for each language to avoid passing down the model everywhere.
- Coding takes more time but things don't gradually turn into spaghetti and refactoring is a breeze.
After the initial functional struggles it feels like writing "the code of your life". Overall, a very positive and fun experience and I don't regret us betting on this new fairly untested technology. And none in the team had lots of prior SPA experience so we had to learn new stuff anyway. Almost as good a tech choice as when we decided to use gevent or MongoDB but for a very different problem.
This was for a custom client project and not our main 46elks service which is an API that mostly uses Python.
I didn't have any issues with nesting. Your views unfold to match your model. I found creating sub-models for each "page" within my app to be very intuitive -- these choices became obvious as I started prototyping -- and it's easy to share partials across different page views.
Also, we're hiring! No Elm experience required; over the years we've had many new hires pick it up on the job. :)
Your model (the central state) becomes larger and more complex, but the size of your update function doesn’t expand as much as you’d think.
Meanwhile, the benefits from type-safety, pure functions, and extremely well-designed libraries like elm/parser & elm-UI are really terrific.
The official Elm Architecture tutorial used to contain some examples of encapsulating functionality into modules that would then be reused. The pattern presented there was abused by some folks and some problems that were introduced by state partitioning started to appear. There was a lot of talk about boilerplate and inter-component communication. A lot of these problems could have been avoided by not being so aggressive with the partitioning and so, "thinking in terms of components" has been discouraged ever since.
You can still define components by implementing the model/update/view triplet pattern but the main recommendation is to do it only when this is unavoidable (e.g. Pages or highly complex widgets).
And what do you do about generic reusuable components that need state? Say a typeahead search that needs to track the input string and the list of results from the server?
A normal practice is to have a model per page. For e.g. https://github.com/rtfeldman/elm-spa-example/blob/master/src...
And you can have another item next to it for global state if you need to.
https://github.com/ohanhi/elm-shared-state explains one way to do this
The Elm language and framework really do live up to their promise: it's a delightful development experience. Despite rewriting our entire 4-year-old UI we've had remarkably few bugs despite multiple large refactors. The whole team was new to Elm when we started, and now everyone's an advocate.
Also, if you're interested in working in Elm and making a measurable difference in student learning, we're hiring!
You can isolate your side effects somewhere, but they still have to respond to actions/events/messages so if your time traveler dispatches actions you'll end up making duplicate requests.
Elm is incredibly reliable (that's why we experimented with it in the first place) as well as very productive once you get used to the Functional paradigm.
Refactoring especially is a breathe (as long as it compiles, it usually works) and we didn't need to test the frontend for it to stay sane.
We even started covering more and more of our admin's old views with it and are on our way towards an SPA.
You have to see the implementation of libraries like https://package.elm-lang.org/packages/dillonkearns/elm-graph... to really appreciate what elm is trying to do. Maybe even read this https://package.elm-lang.org/help/design-guidelines
So every package is documented and quiet easily understandable. So whenever I need to go back into angular land - most times dependencies don't even have any docs maybe a readme if your lucky...
The language is pure joy to work with. No issues with "deeply nested components", just use a function to render a "component", add data needed for the rendering as a function parameter, handle the events emitted from the "component" in the update-function.
I’ve used ports and custom elements to integrate JS code.
I’m convinced Elm is an excellent way (and my preferred way) to build frontend apps.
The learning curve is a bit tricky. I think the path to toy apps is straightforward and fun. The path to “real world” apps requires a bit more effort. Expect to do some homework, hang out in forums etc.
About nested module, they are not necessary at all. Check "The life of a file" by Evan Czaplicki - create a new file if Model/Update gets too big, there is no need for upfront component partitioning.
Although it's recommended to not build components with states, you could still do it if you think it's necessary for your app. I documented the codebase structure I used at; https://azer.bike/journal/elm#codebase-structure
What are the reasons you wouldn't consider using it going forward?
What we liked:
- the compiler: not only does it prevent a lot of bugs, it allows us to refactor large portions of the code base fearlessly. Also, the compiler messages are often very easy to understand.
- the documentation: the guide is excellent.
- The Elm architecture: the fact that this is the only framework that you can use makes things easier, notably for junior developers who are not lost into a sea of choices.
- The Slack community is very responsive, notably via its Slack record channel: https://elmlang.slack.com/. I also found it to be quite friendly.
What we struggled with:
- In our experience, the hardest part has been to deal with JSON Encoders / Decoders. But it got smoother when we got used to it.
- The fact that the 0.19 version was new had some negative effects, eg obsolete docs or unavailable tools. It has gotten much better by know.
If you're new to functional programming Elm will require some getting used too, but we quickly got 4 developers of various experience / backgrounds up to speed. I believe having one developer who already knew Elm and advocated / taught it the beginning helped speedup this process a lot.
About nested components: as stated before, they are much less necessary than one might think; but otherwise this post might be helpful: https://email@example.com/the-translator-pattern-a-model-...
Last but not least: if you are interested in working in Elm (or React), we are looking for creative frontend developers !:
Most recent example: https://www.pavoq.com a SPA built with Elm and Rust
IBM App with Elm for most of the front-end: https://discourse.elm-lang.org/t/ibm-releases-elm-powered-ap...
[Disclosure: I'm an IBMer - but not involved on this project]
Re deeply nested components: we have components about 2-3 levels deep, and while it's a bit more code and boilerplate than having a flat model, it's been just fine. Don't worry about it.
Rather than relying on other people's opinion a better experience for you would be to port one sub-component of your existing system to elm and see how it goes from there, while you gain trust and experience in the new system.
The ecosystem offers some trivial packages, but the hard stuff is missing. JS interop is intentionally quite painful. I'd consider it sufficient for fairly trivial SPA use, but lacking for anything large or anything requiring browser APIs.
The language development is a big red flag: virtually all commits have been made by a single developer. There are no RFCs, and the future direction of the language is very unclear. Any discussions about potential improvements are immediately killed. Releases are infrequent, and may cause very significant breakage, depending on your application.
It's probably the best thing in the JS ecosystem in a long time and i really want it to succeed and to use it more, but the current state isn't that great. Maybe in a year or ten.
The problem with having a bus factor of 1 is that no one sees it as a problem until it becomes one.
- The experience has been terrific.
- Best compilator I have ever worked with.
- Mostly great tooling. Love the auto-formatter.
- Great libraries
- No major issues, some small itches but nothing that can't be worked around.
- No runtime errors in 18 months.
- Very maintainable code
- New team members quickly becomes productive in Elm, even with no prior experience (It helps to have someone with experience on the team to guide the learning and answer questions).
When it comes to nesting we sometimes make use of a component-like pattern, where we have modules with their own state, messages, update and view functions that are used by the main functions. To see an actual example of this check out this SPA example: https://github.com/rtfeldman/elm-spa-example/blob/master/src...
1. An amazingly helpful compiler. The best I've come across for identifying problems, describing them in a meaningful way, and suggesting how to fix.
2. A wonderful synergy between language and architecture. Various others have copied Elm's model-view-update architecture (React, F# SAFE) but as they're libraries, they don't feel quite as well-integrated.
3. An excellent, helpful community.
5. Some nice libraries. Elm-ui, for example, provides a different way to do lay out (no CSS). Again that's not for everyone - but useful if you're not a CSS guru.
6. Strong static typing with powerful type inference.
There are definitely downsides. The full-on, no-compromises immutability means there's some overhead in dealing with e.g. http request/responses or js interop. But there's corresponding value in the resulting guarantees in the code.
As a newbie I'm really taken with Elm; it's a refreshingly stable and integrated enclave in the otherwise volatile front end space. YMMV of course
The redux-like (well, more like redux is elm-like) is baked into the type system so you know it will be done "right".
And thanks to the above, there's less bikeshedding within the team from people who want to reduce boilerplate for bad reasons and make the app worse.
Can you, for any button in your interface, at any point in time, reliably calculate at runtime what your page's data model would look like, and if there were going to be any side effects, if you were to click on that button - but without clicking on it or invoking its callbacks (which could potentially change all kinds of things)? You can do that with Elm (more info here - https://lukeplant.me.uk/blog/posts/two-experiences-with-elm/ ), due to The Elm Architecture and everything being immutable.
(In theory you could do this in TypeScript, but it would be a lot of work and its reliability would depend on everyone coding everything a certain, very unnatural way, with no help from the compiler).
I'm using Elm in production, version 0.18, it is an extremely robust way to make front end code. You just don't have runtime issues.
So for me, if I can't find a way around the restrictions, they may kill my ability to being able to keep my code nicely architectured (i.e. using The Elm Architecture). I may be forced to switch to something like ReasonML with bucklescript-tea - https://github.com/OvermindDL1/bucklescript-tea
The codebase is around 60k LOC for the front-end and we've settled on a few patterns that help with reusability and managing complexity.
- We have moved to a very flat architecture where most things are at the top level of the state tree
- We use web components to wrap up things that are a pain in Elm (like exit animations, fancy text editors, interop with various JS libraries)
- Our reusable components take Configs so that the components are not tied to one Msg type, different pages have to provide hookups for those messages but it allows us to reuse components on different pages pretty easily.
Refactoring with confidence is possible! I have worked on large React/Angular apps where it was nigh impossible to refactor something without breaking a bunch of others things (often without a way to know they are broken aside from a full regression test). Typescript has improved things quite a bit, but it can't give you the same guarantees as Elm can.
As for difficulties we've run into:
- The virtual dom diffing slows down as the number of nodes grows, for our live chat we can have thousands of people chatting at once and diffing between the previous/next set of nodes can easily exceed the time we have for the next frame. We've taken to reducing the scrollback available in chat, virtualizing the list is another option.
- The time-travelling debugger is cool to show off but has some limitations that make it less useful in practice. If the Msg is long it gets cut off and there's no way to see the whole thing. There's no way to exclude or filter messages, if you have timer running the message list quickly fills up and it is difficult to find the Msg you want.
- When we were on 0.18 compile times were a large source of frustration, we ended up using a forked version of the compiler to improve things a bit, I wrote about it here https://medium.com/@antewcode/faster-elm-builds-e0669580ee67.
- There is little published information on what is coming down the pipeline, timelines for bug fixes, features, etc
This final one hasn't been a big problem in practice, but the prohibition on effect managers and native code in the community leads to teams all over the place reimplementing the same thing (e.g. interfacing with LocalStorage). It has never been possible to publish code using ports or native code to Elm's package manager, which I think is a good thing, but 0.19 also removed the ability to compile them locally without forking the compiler. I think I am in a very small minority in the community that thinks that effect managers are not evil and can be used for good.