5 Things you didn’t know about Elm

Ilias
Ilias Van Peer
Published in
4 min readApr 27, 2017

--

Or perhaps you did. Reminders can’t hurt, though.

1. Changing types in record update syntax

Record update syntax { a | b = c } is restricted in various ways: you cannot add or remove fields, but only change the values of fields. In that regard, they are very much like labeled tuples: the general shape can’t change. However, here’s something you can do that may come as a bit of a surprise: changing datatypes.

Naturally, if you have a type alias that says some field needs to be a certain type, and you change that type somewhere, the resulting record will no longer conform to that type alias. Here are a few examples illustrating that.

When is this useful?

As an example, imagine we use some browser detection library in JS like platform.js which can potentially result in null for every value. Let’s say we’re also taking a seed for a random number (because we don’t trust using the current time). We can do that as follows:

2. Ports and flags don’t require automatic conversion

Both ports and flags do automatic conversion of raw JS values to Elm types; but that comes with limitations, for example custom union types.

Json.Encode.Value to the rescue! A Value type encapsulates a raw JS value; so annotating your ports or init function to accept a Value basically means that Elm won’t do its automatic conversion for those values. You will, however, need to handle the decoding and conversion yourself.

The same goes for outgoing ports, where you can handle the encoding yourself before passing the value of to the port.

Automatic conversion is sort of a relic from times when there wasn’t a streamlined Json.Decode library, and it was basically the only way to handle foreign values.

When is this useful?

  • Decoupling your Elm types from your JS types
  • Handling union types
  • Control over what happened when you receive bad values — rather than an error, you get to graciously handle it in Elm.

3. Unsubscribe from outgoing ports

Let’s say you have an outgoing port in your Elm app and subscribed to it from the JS side. How does one do that?

You unsubscribe of course! Note that you need to pass the same callback to unsubscribe as you originally passed into subscribe — similar to working with event handlers. There’s even some magic so you can call unsubscribe from within your subscription handle, meaning you can pass this and unsubscribe without clutter!

As illustration, have a look at the following Ellie. After 5 clicks, they will no longer be printed to the console.

4. Pipelines are free!

Or, more correctly — the right and left function application operators |> and <| are special cased in the compiler so they don’t cause any overhead.

Thanks to Richard Feldman for this little gem.

Naively, a |> b would compile into a function calling the |> operator with a and b as arguments; after which the |> operator would call b a. Under that assumption, simply writing it as b a instead would be more performant. However, that’s not actually what happens — the compiler knows about <| and |> and gives the special treatment; basically inlining their operation.

This means that pipelines, which improve readability, don’t come at some sneaky cost; they’re free!

5. Phantom types are a thing

Phantom types are types in which type variables are declared on the right-hand side that aren’t used in the left-hand side. As an example type MyType a = My String where it’s clear that a isn’t used. This somewhat weird construct gives us the ability to express certain constrains about the values without directly affecting the values.

Thanks to Martin Janiczek for brining this up in the Elm-lang slack earlier!

For example, it becomes possible to create a FormData type that is Unsanitized by default, but allows sanitizing the input using a user-provided Sanitizer and restricts submiting to Sanitized data, like follows.

Now, on its own, this isn’t particularly useful — the same could be accomplished by putting the data in Unsanitized and Sanitized and throwing away the whole FormData type. However, this becomes useful when dealing with functions that don’t care about whether or not the data is sanitized, but just need a FormData a and can work on that type. In order to do that with two distinct types, you’d have to write different functions and give them different names, and double the maintenance effort.

Googling around for phantom types will probably offer you a whole host of other use cases than this one, definitely worth checking out.

So there you have it, some neat things you might not have known about Elm. If you enjoyed this post, feel free to check out my other posts and don’t hesitate to click that green little heart!

--

--