Yan Cui
I help clients go faster for less using serverless technologies.
I saw this tweet on my timeline the other day..
which reminded me again to look at Elm and I’ve spend the last week or so getting myself immersed with this wonderful little language built around the idea of functional reactive programming.
My first impressions of Elm so far have been very positive, there are some genuinely interesting things here, such as its Record and Signal types and its interactive debugger which is heavily influenced by Bret Victor’s work.
The Basics
Syntactically, Elm looks like many functional language (and Python) out there – no curly brackets, whitespace matters, etc. – which is perhaps unsurprising as there’s seems to be a strong Haskell influence here.
Tuples
Tuples are enclosed in parentheses and separated by comma (,) – e.g. ( 4, “3” ) is a tuple of the integer 4 and the string “3”.
You can also use the helper functions (,) (,,) (,,,) … etc. to construct tuples with the required number of items.
Records
At first glance Elm’s record type looks very similar to F#’s record type:
- both are lightweight labelled data structure
- both support pattern matching
- fields are immutable (but F# supports optional mutability)
- both support copy-and-update semantics to create a new record based on an existing record
- both support polymorphic functions (though in F# it’s more idiomatic to use members instead)
However, upon closer inspection you find some interesting additions with Elm, most notably:
- extensibility – besides the copy-and-update semantic, you can also add and remove fields at the same time
- records uses structural typing – a function can accept records of any kind so long they have the required fields (e.g. whoAreYou { name } = … takes any record that has a name field)
- record type aliases can be composed together
Have a look at my last post to see a more detailed run-down of the similarities and differences between records in F# and Elm.
Algebraic Data Structure
Elm supports algebraic data structures (i.e. F# discriminated unions), there are a number of built-in algebraic types such as the Maybe type. You can also define your own algebraic types using the data keyword.
F# Pipes
It’s also nice to see F#’s pipes make an appearance in Elm and that Elm’s creators are receptive to adopting F#’s functional composition operators << and >> too, if adopted I really think they can make code that composes functions more readable and intuitive.
Pattern Matching
Elm supports the case..of syntax (another example of its Haskell heritage) for pattern matching against variables as you can see from the snippet above.
Elm doesn’t have when guards in its pattern matching syntax yet, but you can work around this by using the multi-way if expressions for now, which actually reads like pattern match clauses rather than the traditional if-elif-elif-else.
Elm also supports pattern matching against tuples, records and lists, etc. in much the same way as you’ve seen in F# or Haskell, for instance, you can pattern match against a record using the { fieldName1, fieldName2, … } syntax:
Anonymous Functions
Elm uses the same syntax as Haskell to define anonymous functions.
Currying
As you’d expect, Elm supports currying.
The standard library also has two helper functions – curry and uncurry – to help you change how arguments are passed to a function, which comes in handy sometimes.
- curry : ( ( a, b ) –> c ) –> a –> b –> c
- uncurry : ( a –> b –> c ) –> ( a, b ) –> c
let..in syntax
Like many other functional languages (Haskell, F#, etc.), in Elm the last expression of a function provides its return value and whilst F# has moved away from the let..in syntax from its own OCaml roots, Elm still uses the let..in syntax.
You can specify multiple variables within one let clause, e.g.
And you can also define inner functions in the same way too:
JS Interop
Interop with Javascript is provided via ports, which I haven’t played around with much, but you can read all about it here.
The Good stuff
In Elm, much of what you do revolves around Signals (which is Elm’s equivalent of Rx’s observables) and the language (along with its standard libraries) gives you a very different way of thinking about GUI development to the traditional DOM based approaches. If you are familiar with Rx (or RxJs, the Javascript port of the Rx API) or functional reactive programming* in general then you should feel right at home here.
Elm’s standard library comes with a number of built-in signals – mouse positions, clicks, window dimensions, timer events, etc. From these built-in signals you can then compose and map over them, the built-in Signal library provides quite a number of functions to make life easy there.
Lift functions
You can use the lift functions (lift, lift2, lift3, … lift8) to apply a normal function over signals. In Rx you’d do this with Observable.Select, but the way you can combine signals together using the shorthand operators <~ and ~ is really quite elegant.
For instance, you can pass the signals of your mouse position and the coordinate of your clicks into a function like this:
And here is what the code above generates:
Pretty simple, huh?
Merging/combining signals
You can use the merge, merges or combine functions from the Signal standard library to concatenate multiple signals of the same type into one unified signal. This is equivalent to Rx’s Observable.Concat operation.
This comes handy when you have multiple sources of inputs of the same kind, or when you use algebraic data type to help unify input signals of different kinds.
Take the Missile Command game below for instance, the game loop follows the simple pattern of: action –> state transition –> redraw updated state. However, there are three different kinds of input that would trigger state transition:
- frames – to draw the game at 50 FRS means every 20ms we need to move the missile along a certain distance along its path as dictated by its speed
- player actions – when the player clicks on the screen we should launch a new missile towards the coordinate where the user clicked
- enemy missiles – every x seconds we need to launch a random number of enemy missiles
Each type of input has a different set of parameters associated with them, unified by a single algebraic data type.
Signals representing each of these input types can then be merged together with the merge function and passed into the main game transition logic.
In addition, the Signal library also provides a set of useful functions for composing signals, including foldp, count, keepIf, dropIf, keepWhen, and sampleOn. Again, all of these can be mapped directly to Rx extension methods.
In fact, all the capabilities of working with signals I have seen in Elm can be found in Rx and by extension, in C#, F#, Java, Javascript and Dart thanks to the widespread reach of the Rx API and the effort by the developer community to adopt it.
However, because Rx exist as a library in those languages, it doesn’t force you to fundamentally change the way you view UI development because you always have a way out whenever things get uncomfortable (which should be often, if you’re challenging yourself to go outside your comfort zone). In Elm, however, there’s no other way, so you have to think ‘reactive’ all the way through. From a practical point of view this might sound restrictive, but there’s really no better way to learn the functional reactive paradigm than to fully immerse and commit yourself to it.
The Debugger
If you have been following Bret Victor’s work in recent years (which amongst others have inspired projects such as LightTable, Khan academy’s UI design as well as Elm’s online debugger), you might have had to repeatedly scrape pieces of your brain from the walls thanks to Bret’s super awesome demos.
I recommend giving this page a good read to see how it works and how Elm makes it possible.
Here’s a short demo video to show its time-travelling capabilities in action.
The Not So Good stuff
- The language is still very young so expect lots of breaking changes and broken examples from around the internet as it evolves. Personally, I think this is a good thing, it’s better for the language to learn and evolve away from its early mistakes than to forever live with the sins of its youth.
- IDE tooling is lacking. Whilst the online editor and debugger can provide hints (i.e. function signatures and a link to the online doc for the function, see below) for functions and types from the standard libraries, they don’t work on user-defined functions and types. You can also just use any text editor such as Notepad++ or Sublime Text that gives you syntax highlighting (Haskell highlighting usually works pretty well) but I usually look for much more support from my development environment and seeing Bret Victor’s work only raises my expectations and Elm’s online debugger is a decent first step.
- If you’re developing with the online debugger then be sure to save your changes regularly, to a source controlled file preferably. As the debugger executes your code it collects events along the way, which is how it enables this awesome time-travelling capability to go back in time to see the effect of your changes as you make them. With the Auto-Update option turned on you get to see your changes reflected right away which is really nice, the downside being that if it has collected a fair number of events it can hang/crash the browser and you lose your unsaved changes along the way. Hopefully that’s a painful lesson you won’t have to learn the hard way.
- There are a fair amount of guided tutorials for the basic things, but once you start to get into the intermediate/advanced topics you’re mostly left to work out how to do things based on available examples that are barely commented and those auto-generated docs for libraries really don’t do the readers justice. This is common for new languages, I have had the same experience with Dart too despite Dart being much more mature and have a bigger community around it (and not to mention having a company like Google behind it).
Missile Command
Finally, to aid my learning process, I put together a simple implementation of “Missile Command” which was fun to do and helped give me a sense of direction when trying to figure out what I can and cannot do in Elm.
I have written this in a way that’s perhaps more verbose than the official examples, with lots of type annotations to make up for the lack of support from the online editor/debugger. Even so, the source code came in at less than 250 lines, which is really encouraging, imagine the level of productivity a more competent Elm developer is able to achieve!
The source code is available on Github here, you can copy the content of main.elm into the Elm online editor to try it out.
* on the topic of reactive programming, I came across this talk by Erik Meijer, the father of Rx, titled Duality and the end of Reactive and it’s well worth a watch. Without going into too much detail and robbing you of the joy of making your own discoveries and conclusions from it, my key takeaway from the talk came from Erik’s closing remark:
Reactive is Dead, long live composing side effects.
Now, I don’t think Erik is telling us to stop using Rx or the reactive paradigm that he has done so much to popularize, but to shift our focus from the act of doing them (composing signals/observables and then writing code that reacts to them) to the why – which is to help us rationalize side effects and compose them, and making the implicit relationships between side effects explicit.
Related Reading
Erik Meijer – duality and the end of reactive <- (must watch)
Elm’s time-travelling debugger
Academic paper – Extensible records with scoped labels
Contrasting F# and Elm’s record types
Dart – emulating algebraic data type
Udi Dahan – Making roles explicit
Bret Victor – Inventing on Principle
Bret Victor – Stop Drawing Dead Fish
Bret Victor – Media for Thinking the Unthinkable
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.
Pingback: F# Weekly #29, 2014 | Sergey Tihon's Blog
Pingback: Elm – building a version of Snake in under 100 lines of code | theburningmonk.com
Pingback: Year in Review, 2014 | theburningmonk.com
Pingback: Fear and Loathing with APL | theburningmonk.com
Hello there, Yan. I’m researching Elm right now while we’re reading the Elm chapter in “Seven More Languages in Seven Weeks” in a developer book club at work. This blog post is a treasure trove of helpful information! Thank you for taking the time!