scrollback-limit = 512000000
keybind = super+f=write_scrollback_file:open
It writes it to a temporary file and then opens the file in the default text editor when I hit Cmd+F.104 karma · joined February 10, 2014
scrollback-limit = 512000000
keybind = super+f=write_scrollback_file:open
It writes it to a temporary file and then opens the file in the default text editor when I hit Cmd+F.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.
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.
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.
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 wanted to give a shout out to one of Jeroen's other projects in the Elm ecosystem, elm-review (https://package.elm-lang.org/packages/jfmengels/elm-review/l...), it is an excellent linter/fixer and allows writing your own rules as well.
In my experience it results in more consistent designs because everything must come from what you make available in your tailwinds config.
There is a ticket open for adding support into Safari, but it probably won't happen anytime soon. https://bugs.webkit.org/show_bug.cgi?id=182566
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 don't know if there is a formal name for this pattern, but we've been calling in the "un-attr" pattern, you define an opaque type like
type Attribute msg
= Attr (Html.Attribute msg)
And then expose functions that allow you to set attributes you whitelist. In the JS you use setters to hook into when the attribute changes and respond to it (https://github.com/ellie-app/ellie/blob/master/assets/src/El...)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.
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.
I've been working in Elm for about a year now and it is a joy to work with, moving from vanilla JS/Angular/React to Elm has been a boon to productivity and I actually have confidence that when I refactor something and have fixed any compiler errors that it is going to work.