Error Handling in Elm

And How it Contrasts with Javascript

In Javascript there are a handful of ways to deal with situations that don’t follow the happy path.

You could be disciplined by protectively checking values and return safe responses .

by protectively checking values and . You could be exhaustively disciplined by doing TDD as much as possible.

as much as possible. You could use try-catch and throw to mimic languages that have checked exceptions.

to mimic languages that have checked exceptions. You could do nothing and let runtime errors have a detrimental impact on user experience.

When writing Elm apps you’ll inevitably run across a situation when you need to handle a non-happy path situation. Your first intuition may be to look for the strategy that you used on that one Javascript project, but that other way may not be available to you with Elm.

In this post I’ll outline the types of “error” situations you can find yourself in when writing Elm applications and I’ll describe how Elm makes handling these situations pleasant and safe.

Unexpected Types

The first type of error that you’ll find in Javascript is a type error. It’s the notorious undefined is not a function or cannot read property “something” of undefined.

In Elm applications, you will never see this error. Elm is a statically typed language and it has the following protections in place.

The Compiler

The compiler will guarantee that if you declare a constant as a String, for example, that constant could only ever be treated as a String. It can’t be an Int. It can’t be null. It can’t be undefined. If you attempt to make it anything other than a String the application will not compile.

Immutable Constants

Elm doesn’t have variables that can be reassigned to different values or types. Elm has immutable constants, or perhaps better thought of as zero argument pure functions. As such, your declarations can’t change types out from under you.

Maybes

Many languages treat null and undefined as subtypes. This is the infamous billion dollar mistake. In Elm the equivalent of null is called Nothing, and it is one of the tag names in a tagged union called a Maybe.

Elm treats nullability as a separate type

Said differently, Elm treats nullability as a separate type. As a result, the concept of null must be handled explicitly as part of the type system, thus preventing many common runtime errors.

Unexpected Data

Applications receiving input from the outside world will inevitably receive data in a shape or type that it wasn’t expecting. In Elm there are a couple of entry points where data from the outside world can be received.

Messages from Html.Event handlers

Messages dispatched from Html.Event have specific types and are therefore guaranteed to supply the Elm app with a Message and a payload of the correct type. This is yet another example of the type system guaranteeing that you’re going to handle the right type of value without you doing any additional work.

Messages from Tasks

Tasks are how you perform most side-effects, such as performing Http requests. One of the arguments that must be provided for performing an Http request is a Decoder. A Decoder is a composable type that Elm uses to transform JSON into a type that you can work with. Elm accomplishes this by handing you a Result which is a specialized either type that will either hold an error (a failure to transform the JSON) or a success value (the transformed value).

Elm hands you a specialized either type, called a Result

If there is an error then the compiler will force you to handle that error. This is your opportunity to update the application state with an indication that unexpected data was received.

Async Failures

When making HTTP requests those requests can ultimately fail for a variety of reasons. With Javascript you’ll pass a success callback to handle successful responses and there’s an optional failure callback to handle failures.

Elm is different. Rather than exposing an optional failure callback you’ll instead be asked to provide a callback that accepts a Result (the same specialized either type discussed above) as input and that callback returns a Message that will likely be different depending on the Result.

What this means is async errors have to be explicitly handled, as opposed to being accidentally forgotten.

Preventing errors with carefully designed data structures

By far, my favorite type of error to handle is absolutely no error at all. If a bad situation can occur, and you can reasonably prevent it, why not prevent it?

The way to accomplish this can best be elaborated by Richard Feldman’s Make Impossible States Impossible talk but my current two favorite examples that apply this idea are Kris Jenkins’ RemoteData package and Max Goldstein’s elm-nonempty-list package.

Here’s a demonstration of using RemoteData.

RemoteData Example

In using this package we’re preventing a certain “error scenario” where the view could be out of sync with what is actually happening during a request. Because RemoteData is a type in the Model, the compiler will force you to handle the four different Tags that the RemoteData can be, thus forcing you to explicitly handle in the view layer the different parts of the asynchronous request’s lifecycle.

Caveats

I lied in the Unexpected Data section above. There are actually two more ways that an Elm application can receive data from the outside world.

On app initialization with Html.programWithFlags When receiving Messages from ports

In my opinion, the current version of Elm (0.18) doesn’t handle these situations so well when it comes to error handling. When using programWithFlags it’s possible to have a runtime exception when the application is initialized with a value that it’s not expecting. When using a port subscription it’s possible to have a runtime exception yet again when an unexpected value is sent to the Elm application. (open the console to see the errors)

I asked about both of these specifically and the response I got indicated that ports and programWithFlags predated Decoders, and that we shouldn’t expect that design decision to change in the near future, which is fine.

There was also a helpful suggestion to use Json.Decode.Value. In other words, declaring the data as a JSON value will force you to use a Decoder to turn the JSON value into a type that your Elm application can use.

So the best answer I have to these two situations is to be disciplined and use Json.Decode.Value when using programWithFlags and port subscriptions. It’s a pattern that’s consistent with explicitly handling errors and preventing runtime exceptions.

Concluding Remarks

I find handling error situations in Elm to be a rather pleasant experience. Elm will force you to handle errors essentially by leveraging the type system which is great both in its simplicity and the resulting developer experience.

I suppose one of the bigger takeaways from this post is if you happen to be working on a Javascript application you can employ these same strategies by adding a static type implementation that also has the equivalent of tagged unions. I realize it’s easy to say and harder to do. But for me at least it’s good to know that I can leverage some of the benefits that I get with Elm by introducing static types to the Javascript applications that I work on.