There was a similar project to `go fix` with elm-upgrade (https://github.com/avh4/elm-upgrade). We migrated a ~100k LOC app at work from 0.18 to 0.19 and while it was tedious, the compiler really has your back and makes it easier.
We weren't bit by the removal of native/kernel code, but it definitely turned a lot of people away that may have been committed to Elm before.
We also ported our Elm codebase from 0.18 to 0.19. It took us almost a week working almost 24/7 across two timezones to make the damn thing compile again. Did not see the UI for the entire week, but once it compiled it (mostly) just worked like nothing had changed (that's after 429 files changed, 16422 insertions(+), 12116 deletions(-)).
Wow! Apologies if this is answered in those notes but my quick question is: Did you make extensive use of Debug.todo to cordon off sections of the app that wouldn't compile in order to work on and "see" the other sections in the meantime? That's what I do on my small elm apps, e.g. put whole features or functionality behind Debug.todos.
I might be wrong but I think Debug.todo was introduced in Elm 0.19. But in any case we were not aware of it at the time and didn’t use it. Btw Debug.todo is amazing and we do use it now sometimes.
I remember hearing Mordrax speak about the upgrade, quite herculean!
At our company it was much less drastic. At the time we had about 35kloc of Elm and it took me one evening of Vim macro frenzy. The trickiest things we encountered were an elm/http API change and temporarily vendoring packages that didn't yet upgrade themselves.
Mike I just wanted to say I really enjoyed using D3 and I was also very impressed when reading the source code. It’s an excellent model for good programming in JavaScript that served as a great example for a younger me.
Thank you to you and your team for D3 and for this library. I’m sure I’ll get lots of use (and freelance gigs) out of it.
That "Why I'm Leaving Elm" post comes up on most Elm discussions, and I would implore anyone to give it a try for themselves before deciding against it. I've been working in Elm professionally for a few years now and I think Luke's experience in that blog post is certainly atypical.
For writing web applications Elm is an excellent language to work in, the compiler is friendly and fast, there are basically zero runtime exceptions, refactoring is a breeze, and it really shines on larger code bases. Web-components are discounted pretty quickly in that post, but they really fill an important gap where ports are awkward.
In the years I've been using it I haven't run across a problem that I couldn't solve in a nice way. That is not to say my experience is universal, but the negative posts tend to garner a lot more attention than positive posts, and I'd feel bad if someone were to skip over what is an awesome project due to them!
> I think Luke's experience in that blog post is certainly atypical.
> the negative posts tend to garner a lot more attention than positive posts
On the other hand, my team was very unsatisfied with Elm and when we bring those issues to community we were quickly asked to leave. Of course it is fair but please note that negative opinions are discouraged and thus not common. We just disengaged from community and started elsewhere.
Sorry, I didn't mean to say that nobody has negative experiences with it, but that "X considered harmful" style posts tend to get more attention on sites than positive experiences generally do.
What was something that left you unsatisfied with Elm? The biggest one for me until relatively recently was handling state updating state from leaf components, it was fairly cumbersome to have `(Model, Cmd, ExternalMsg)` in lots of places. At work we recently switched to the Effect pattern (https://sporto.github.io/elm-patterns/architecture/effects.h...) which has alleviated a lot of the overhead.
FWIW the creator of the Effect pattern has some pretty deep reservations about Elm (http://reasonableapproximation.net/2019/10/20/the-effect-pat...). His reservations end up being the same ones that get hashed out all the time over HN (JS interop and the development model).
I've come to view Elm as someones personal compiler project. If you are good with the decisions made then you're going to have a fine time, otherwise you probably won't
I think we are somewhat in agreement, Elm is certainly Evan's language and framework, and if you live within the boundaries of it you'll have a pleasant experience.
I have read all of the bad experience posts on Elm because I've spent a while working with it now and it is always valuable to see someone else's perspective on it. In practice the pain points either haven't really been a problem for me, or I've found a satisfactory way around them using web components, ports, code generation, or other ways.
My experience is just more anec-data and I know it won't represent everyone's experience. I'd encourage anyone hesitant about Elm to give it a go, it's been a joy to work with.
Glad to see more posts on Elm out there, it really is a great language and ecosystem! Like any large project, it has some issues, but in my day-to-day writing code I have never had as much fun, or had as much confidence that my code does what it says on the tin, as I have with Elm.
Tailwinds also acts a design system for your site, you have a set of paddings, margins, colors, font sizes, breakpoints, etc that make up your design system.
In my experience it results in more consistent designs because everything must come from what you make available in your tailwinds config.
I've been working in Elm for a few years, and I feel like I can answer some of the questions.
0.19 introduced a new restriction on Native code (javascript), previously you could compile kernel code in your own projects, now you can't. You've never been able to publish a package using native code to the package site (only packages under the elm or elm-explorations github namespace can).
If you feel strongly about it there are ways around it, forking the compiler, or giving your project a fake "elm/whatever" name so that the compiler will build it, there is also a set of shell scripts on github that allows you to get around the restrictions. I've used it before to make some tooling to track virtual-dom performance.
The biggest miss in the post in my view is not looking more at using Custom Elements for the internationalization needs. Having a custom element where you pass in a posix time and have it formatted using the Intl APIs is very possible, and it also allows you to wrap up the interface between Elm and JS in a nice, type-safe way.
The 0.19 upgrade was fairly large, but manageable. Our app was around 70k LOC when we upgraded from 0.18 to 0.19, the upgrade was fairly smooth, and it hugely improved our build times. I had actually forked the 0.18 compiler to improve build times, full builds took around 2 minutes, and incremental builds around 45 seconds. In 0.19 full builds take 4 seconds and incremental builds less than a second.
I share in some of his experiences as well, I have had posts deleted on the Elm Discourse page for mentioning a way for someone to run a fork of a core package to get a fix. I stopped working on a private package manager for Elm after someone described an existing solution as a hostile attack on Elm.
Overall the benefits of Elm still outweigh the downsides for me, no other language I have used has made development a joy like Elm has, refactoring is honestly fun, change whatever you want, follow the compiler errors, and at the end everything works again. Packages are generally high quality and work, an Elm package that hasn't received any updates in a year probably isn't abandoned, it's finished. Coming back to code I haven't worked on in a while is easy, the type system has my back, and most Elm code looks similar due to using the elm architecture and elm-format.
> I've been working in Elm for a few years, and I feel like I can answer some of the questions.
Who's questions? The author didn't have questions about those things.
And I'm quite certain that the author is well aware of being able to edit a shell script to get around that restriction—and even if they don't, that doesn't change the content of the post.
> The 0.19 upgrade was fairly large, but manageable ..
For _you_, but not for everyone. Can you discount all of the experiences for those whom it wasn't manageable, and for those for whom it was impossible?
> .. an Elm package that hasn't received any updates in a year probably isn't abandoned ...
This wasn't always true. It's one of the reasons why the community forked several packages that authors had abandoned.
> .. most Elm code looks similar due to using the elm architecture ...
Oh dear. There's still so many approaches, and packages, about the different ways that people approach it.
* I wrote Elm professionally for 3 years from 2017-2019
I would say the name is fine in terms of the Elm runtime. This is not about JS modules, but about calling the JS API of the browser and also managing some state there.
> The biggest miss in the post in my view is not looking more at using Custom Elements for the internationalization needs
? This wasn't really a technical blog post so I don't think this was a "miss", the points being shared around i18n efforts were meant to support the primary message of the article.
The company I work at (https://halogen.tv) has been using Elm in production for a few years now, we have around 90k LOC in Elm for the front-end, and it has mostly been a pleasure to work in.
The biggest upsides in my view are:
- The compiler is your best friend, the error messages are great, and you can enforce a lot of things with the type system. Have a list that should never been empty? Use a NonEmptyList, need to manage a selection? Use a Zipper/SelectList. Forgot to handle a possibility in a case statement? The compiler will catch it for you.
- Refactoring, even large scale refactors, are tractable and actually fun. Change the architecture in whatever way you want and follow the compiler errors, when you fix the last compiler error things usually work like you want!
- (Almost) No runtime errors. There are still a few rough edges where you can get a runtime exception, but they are uncommon.
It is not to say it is all roses, there are negatives as well:
- Lack of a roadmap or timeline, there is zero visibility into what is being worked on and when things may happen. This has been a deliberate choice by the core team I think because when they had a roadmap and parts of it didn't fit into the next release people were angry.
- Bugs can take a long time to be fixed, even if there is a PR that fixes it, it is unlikely to be merged.
- Experimentation is discouraged. The 0.19 update removed the undocumented, unsupported, here-be-dragons hooks that allowed writing effect managers except for repos in the elm or elm-explorations organizations on github. I agree with not allowing those modules to be published to the elm package site, but intentionally blocking people experimenting on their own is unnecessary in my opinion. Of course you can always fork the compiler and remove the restrictions.
- Ports are a pain. We generally use custom elements in place of ports where it makes sense, but they are really a hack around some of Elm's pain points, if I could do it all nicely in Elm I would.
> - Lack of a roadmap or timeline, there is zero visibility into what is being worked on and when things may happen. This has been a deliberate choice by the core team I think because when they had a roadmap and parts of it didn't fit into the next release people were angry.
this is a bit sad (the fact that mobs are still mobs)
in any case I wonder if they couldn't make some kind of core partner club with people that have been long and deep Elm users
I've always seen Elm as being between a rock and a hard place socially. On the one hand, a lot of people with no FP experience dismiss it for being a "weird" FP language. On the other hand, a lot of people with FP experience dismiss it for not having some FP feature they really want (e.g. one that keeps coming up and just won't die is typeclasses).
Also there is a core Elm team consisting of something like half a dozen people, unless you mean a partner club of e.g. tech teams that use Elm.
I have an immense amount of respect for Evan Czaplicki for how he tries to thread the needle here and the huge amount of thought and craft he puts into the UI and UX of Elm to make it accessible. Unfortunately you can't please everyone (hell he doesn't even please me with all his decisions, the gall of him! ;)) and Elm has had its share of vocal naysayers. Elm may or may not die out, but the impact it's had on raising the bar for the UI/UX of dealing with a compiler has been IMO one of the most valuable advancements for statically typed languages in recent years.
> a lot of people with FP experience dismiss it for not having some FP feature they really want (e.g. one that keeps coming up and just won't die is typeclasses).
You still can't write a generic sort function. It's not only typeclasses, there's also no module system and Evan is patronizing about it.
> this is a bit sad (the fact that mobs are still mobs)
Not that people in open source can't be entitled, but the way Elm handles the community is awful and patronizing. Also they make too many breaking changes for me to write code in Elm.
My team has had a similar experience - the gap between 0.19 and 0.19.1 was really frustrating.
I'm curious what your problem is with ports? In my experience it's a nice API for interacting with the outside world. Or are you referring to DOM features that haven't been mirrored in Elm yet?
For me the annoying thing about ports is that it makes any interop with a JS library of pure functions frustrating. For example, let's say I wanted to use the math.js library. I have a string expression I want to parse and calculate into a mathematical result.
math.js has a nice `math.evaluate` function for this, that in theory would have the type `String -> Result ParseError Float`, if I could directly assign Elm types to a JS function. However, I can't actually use the function like that. I must treat it as a pub-sub mechanism and define the requisite messages, state, update functions, etc. in order to use it. This turns what should've been a one line call into potentially dozens of lines that also forces an architectural change in how the calling code actually calls the function.
The only other real alternative is to rewrite the functionality I need in Elm.
I guess Evan really wants that function to get rewritten in Elm - after all it's synchronous.
There should be some built-in asyn request/response mechanism for ports, in the style of the Http module where you specify a Msg for the response. This would work for synchronous stuff too.
For years and years people have been asking for "task ports" which would facilitate synchronous but safe js interop.
As far as I can tell the only reason Evan doesn't include something like task ports is to purposely make interop less convenient (but no more safe) than it needs to be, in order to encourage people to write elm. However, I think this decision will end up biting him at the end of the day.
They are fine for a general FFI mechanism, and in certain cases they fit into the code very nicely (e.g. websockets fit the publish/subscribe pattern very well), but for handling events from the DOM or interacting with existing JS libraries it breaks down a bit.
For instance, we have a text editor that wraps Quill so that we can support things like mentions, hashtags, and emojis. We can have multiple instances of the editor on the page at once, so the port needs a way to uniquely identify which instance the event is for, and handle delivering any responses to the correct instance of the editor. I find it a lot easier to partially apply the ID of the editor to the message so that everything is defined where it is used in the code. It is also possible to forget to add the necessary subscriptions when adding the editor to a new page, whereas with a custom element it is all handled internally.
Another case where ports had a bit of an impedance mismatch is on using existing browser APIs, like the Intl module (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...). We wanted to use it to format an epoch time into the user's locale. The only way to call the Intl module from Elm itself is through ports, and the wiring makes the code a lot more complex because you have to send the request out through a port, wait for the response, and then render it. With a custom element it becomes a lot simpler conceptually, we have something like
DateTime.view posix
We also like to wrap up the custom elements with a type-safe Elm wrapper so that we can control what options are available. We have some feeds that have infinite-scroll on them to load more content as you reach the bottom, it uses a custom element that wraps the IntersectionObserver API and fires events that Elm can listen to, so it makes it really easy to use and understand how it is working, in the code it looks like
InfiniteScroll.view
{ onScrollIntersect = LoadMore
, state =
case feed of
Done ->
InfiniteScroll.Done
Failure ->
InfiniteScroll.Failure
Loading ->
InfiniteScroll.Loading
}
Anywhere you want to respond to scroll position you can just add that element in and it will work its magic.
We've been using Elm in production for https://halogen.tv and has been a great experience overall.
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.
- 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.
We weren't bit by the removal of native/kernel code, but it definitely turned a lot of people away that may have been committed to Elm before.