Top 6 ways to make your Elm app crash at runtime
Elm is famous for having “no runtime exceptions” and for the most part, it does a really, really good job! It is possible to write Elm without runtime exceptions, and the majority of users may never see them.
A while ago, I tweeted 9 different ways to make runtime exceptions that I could think of off the top of my head over a series of days. Here’s a little examination of the 6 I have encountered in the wild, and some rules I like to follow to avoid running in to them. I follow these rules to ensure my Elm applications never crash — but sometimes I do it just for fun anyway.
This is true as of Elm 0.18.
Number 1 — Regex
Regex has been broken for a while as it can create runtime errors in Elm. This is the most basic example:
If you go to the Results
tab, you’ll see nothing! That’s because Regex.regex
crashes when you give it an invalid regex. In this case, I started a group without closing it — which means my Elm app crashed. This crashes on load, since Elm is not lazy for top level values, but if you use your Regex inline, then it won’t crash on load — meaning runtime errors can easily make their way into production.
Click on the results tab, then click on the button and see how nothing changes! This is because the runtime error only happens now when the update
function is evaluated. Imagine if this was actually several functions deep! Pretty tricky to figure out.
My rule: only define regexes at the top scope
Number 2 — Arrays
Like Regex, Arrays have been broken for a long time! Here’s a small little example:
Click on the results tab and you’ll see nothing! This is because of the way the core Array implementation implements things. Internally, it uses a structure based around the number 32 to lay out a nested tree of arrays that allow for fast indexing. You can read more about it by exploring a fixed alternative, called elm-array-exploration by Robin. The original implementation was almost entirely written in Javascript, and the documentation and the author have long vanished, like an old oak table in the night. Robin’s implementation focuses on writing as much as possible in pure Elm — and only using JS for the implementation for the core parts needed.
My rule: never use elm-lang/core’s array if you want things to work. Use elm-array-exploration until it replaces core’s implementation.
Number 3 — function equality
Function equality is somewhat of a difficult thing to figure out in a language with partial application. What does it mean for a function to be equal? Well, in Python, I’d say if they were the same function object with the same args, they’d be the same function. In Elm, however, function equality is not supported.
Check the results tab — you won’t see anything. There’s not really much to say about this. Maybe function equality should be supported, maybe it shouldn’t. I’m more of the shouldn’t side, but that’s not really here nor there.
My rule: you probably don’t want function equality. Stick them as values in a dict with an ID of some kind.
Number 4 — Elm is not lazy!
A common mistake I see a lot on the Slack channels is Elm being treated as lazy when it’s not. An example of this is below:
Notice how Maybe.withDefault
might look harmless, but actually, if you go to the results, all you’ll get is a page full of blank. That’s because Debug.crash
is evaluated when you pass the string to it, and without putting it in a function, it just gets run right away — even though we have a Just
!
The fix is to just use a good old case statement instead
case Just 4 of
Nothing -> Debug.crash "This will never happen"
Just x -> x
My rule: never have a Debug.crash that isn’t either in a case statement or a function
Number 5 — not everything should be encoded
In Elm’s core algorithms, there is little difference between a JSON object and a record. In fact, the difference between the two can merely be found at compile time. Union types are a little different — they get a magical field called ctor
, along with _n
for each argument they take. Since we can create these objects at runtime via Json.Encode
, you can make it crash by tricking ==
and a whole bunch of other methods.
Notice that we had to make a new object twice: otherwise Elm uses referential equality and shortcuts this trick. However, I’ve run into problems with this only once or twice — and neither time was shortcutted! I have some more documentation of these data structures here.
My rule: never do ==, toString, etc on Json.Value. Only use functions that look like Json.Value -> _
Number 6 —Invalid Html.Attributes
In Elm-Html, it’s possible to give invalid properties to Html elements. For example, select
can’t be given a type of number
. It just doesn’t make sense! And so, this crashes Elm.
My rule: make sure you know the Html spec (tongue in cheek applied)
Conclusion
These may not all seem like something you run into, but when you do it can be a nasty surprise. We hold Elm to such high standards because of the effort taken to prevent these kinds of crashes. I’m glad that some obscure Json object being encoded and then being equalled is one of the only ways to make an application crash in one of my favourite languages! I can’t say the same of many of my other languages. Can you?