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.
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.