Elm: Any good?

By: on September 22, 2016

I love Haskell. But, like many people who love Haskell, I don’t use it for very much. Wouldn’t it be nice to have the nice properties of Haskell (the compiler checks that your code makes sense before you run it, Quickcheck-style testing, pure functions) but produce production-quality Javascript for client-side web programming?

The most tantalising of the available options is Elm, the brainchild of former Googler Evan Czaplicki. What do you get in the box?

  • Pure functional programming
  • Type inference (like everything these days)
  • Functional-Reactive-Programming-style time-varying values (“signals”)
  • a REPL
  • elm-reactor, which compiles, runs and serves your code when it is saved by your editor
  • a neat DOM creation language. I never understand why people want to use HTML-like syntax as in (say) JSP; it’s horrible!
  • a nice package installer

What, after a lot of shaking, doesn’t fall out of the box?

  • Typeclasses
  • Any IDE that can do refactoring (many can do syntax highlighting)

OK, we can live with that, let’s go!

You can see my work on github. It renders a list of objects that can be reordered by drag and drop. With a bit more effort if could become a proper package, if anyone’s interested.

General impressions

The Elm development experience is very nice; the lack of typeclasses means that the standard unreadable Haskell errors (the Glasgow Haskell Compiler seems to think that nearly every error is caused by a missing typeclass) are nowhere to be seen. In general the error messages from the elm compiler are extremely helpful, but of course type inference gives you nasty problems where it gives some expression a type which you did not mean it to have and then it breaks somewhere else; this seems inevitable.

Pure functional programming gives you the normal nice properties that, because side effects and global variables are banned, you know exactly what each function’s inputs and outputs are, which makes your code easy to reason about and refactor.

The core libraries are pretty small; lots of functions that a Haskeller might expect to be there are not (such as const and splitAt). Writing these or finding them in another package is fairly simple, but at the cost of polluting your code with lots of little functions that don’t get reused because no-one knows where to find them or what you have called them.

The lack of typeclasses I felt in two places:

Firstly, there are many packages that expose an andThen function. In each case this is where Haskell would have made the appropriate type a Monad, then we would be using monadic-do notation to make the code look simpler. With Elm we have to use andThen (admittedly a better name than Haskell’s >>=) by hand, which looks confusing and nasty. On the other hand, if you are unsure about Monads, you can use andThen happily until eventually you feel the need for better notation- this could be the “Aha!” moment you need to understand Monads!

The second place I really felt the need for typeclasses was in testing, more of which later.

Functional Reactive Programming?

You may have read about Elm’s interesting concept of “signals“, which are time-varying functions. These are miles away from Conal Elliott-style Behaviours and Events, being just piecewise-constant functions of time, but this is nice because the simplicity makes it clear what is going on and how it should be used, at the expense of some expressive power.

But before you go diving into Elm signals, know this: you just don’t need to know about signals unless you are going to be hacking on the deepest of Elm’s libraries. Although the set of functions that you can use with signals (merges, maps, filters, fold-over-past-values and so on) seems at first to cover everything you need, eventually you’ll realise they permit only one sensible architecture: funnel all signals you are ever going to need into a single signal and update your model with a single function that does everything. This is encapsulated with Html.App.program and friends, so you should just use those and forget about using signals directly.

But this leads to a worry: If everything needs to be updated in one big function, how can I make reusable components? Is function composition going to be enough?

Reusability

The Elm architecture ties together the following four functions which you must supply to Html.App.program:

  • An initial Model for your App
  • A function for converting your Model to a fresh virtual HTML View every time something changes (Elm will diff this against the current view to perform minimal updates on your behalf)
  • A function for updating your Model according to messages it has received (either from some JavaScript you are running or from onClicks and similar in your View), and producing commands to send to JavaScript (HTTP requests, for example)
  • A function for Subscribing to events from JavaScript (mouse movements, for example) based on the state of your Model

So any component is a four-tuple (or record) of initial state, view function, update function and subscriptions function. Actually I found that there is potentially one more. My draglist does not only need to know these four things about the elements to be dragged, but also what command needs to be sent when a reordering has happened.

Composing components like this is actually very pleasant. Here is the code that puts components consisting of a label, a number and + and – buttons to change the number into a draglist:

import Draglist exposing (init, view, update, subscriptions)

import Html.App as App
import Platform.Sub as Sub
import Platform.Cmd as Cmd
import Html exposing (span, button)
import Html.Events exposing (onClick)
import List

main = App.program
  { init = init <| List.map initItem ["one", "two", "three"]
  , view = view viewItem
  , update = update updateItem repositionCommand
  , subscriptions = subscriptions (\_ -> Sub.none)
  }

type alias ItemModel =
  { text : String
  , number : Int
  }

type ItemMsg = Up | Down

initItem text = ItemModel text 0

viewItem { text, number } = span []
  [ Html.text text
  , button [onClick Down] [Html.text "-"]
  , Html.text <| toString number
  , button [onClick Up] [Html.text "+"]
  ]

updateItem msg model =
  ( case msg of
      Up   -> {model| number = model.number + 1}
      Down -> {model| number = model.number - 1}
  , Cmd.none
  )

repositionCommand from to newList = Cmd.none -- change this to send an HTTP Post or whatever

It’s a bit annoying to have to specify each of these four things instead of having generic Component types that can be combined in a standard way, but we can see here how Draglist’s init function takes a list of models and update takes an update function as well as a command-producing function with an odd type. These are specific to this case, so it would seem foolish to try to genericise this.

It is easy, though, to see how we could write some function that takes a couple of components and puts them together in a div, something like:

par a b =
  { init = let
      (ma, cmdA) = a.init
      (mb, cmdB) = b.init
      in ((ma, mb), Program.Cmd.batch
        [ Program.Cmd.map Either.Left cmdA
        , Program.Cmd.map Either.Right cmdB
        ])
  , view = \(ma, mb) -> div []
      [ Html.App.map Either.Left (a.view ma)
      , Html.App.map Either.Right (b.view mb)
      ]
  , update = \msg (ma, mb) -> case msg of
      Left msgA -> let
        (ma', outMsg) = a.update msgA ma
        in ((ma',mb), Program.Cmd.map Either.Left outMsg)
      Right msgB -> let
        (mb', outMsg) = b.update msgB mb
        in ((ma,mb'), Program.Cmd.map Either.Right outMsg)
  , subscriptions = \(ma,mb) -> Sub.batch
    [ Program.Sub.map Either.Left (a.subscriptions ma)
    , Program.Sub.map Either.Right (b.subscriptions mb)
    ]
  }

This works, but it is perhaps not all that useful because it gives no way for the two components thus joined to interact with each other. You can imagine re-writing this every time you want to join two components together, each time changing the update function to notice if one of the parts has been changed in a way that is interesting to the other part and taking appropriate action. Not nice.

Although I did not try this, if I were to write a big application in Elm I would worry that a seemingly trivial move of a component from one part of the page to another could trigger a distressing amount of tedious re-work.

Some nice publish and subscribe support could be really nice to avoid this. I’m not at all sure how this could be made idomatic in Elm; perhaps the easiest way might be to use a little bit of JavaScript to receive commands and route them back to Elm via subscriptions.

Testing

Unit Tests

Of course I love Haskell QuickCheck. Why test that your function behaves correctly for inputs [2,3] and Just 5 when you could test that it behaves correctly for any list and any maybe? I love the fact that, in Haskell, you can write

prop_reversingAReverseIsId xs = xs == reverse (reverse xs)

And that’s enough to test this identity for a variety of inputs. Being so concise relies on typeclasses, and Elm does not have typeclasses. The equivalent Elm looks like this:

fuzz2 (list int) "reversing a reverse is id"
  <| \xs -> Expect.equal (reverse (reverse xs)) xs

Originally, my love for QuickCheck led me to the elm-check package, which I would urge you to avoid. The equivalent in elm-check is:

claim "reversing a reverse is id"
`that` (\xs -> reverse (reverse xs))
`is` (\xs -> xs)
`for` list int

How anyone can bear to write that is beyond me. elm-check does not play well with Elm’s standard testing infrastructure either, so just use the fuzzN functions from the Fuzz module of the standard elm-test package.

Testing the UI

So we can easily and relatively nicely test our functional code. But what about our components and apps? Sadly, the story here is not so great.

Notice that the signatures for init, view, update and subscriptions functions all have one of Cmd, Html or Sub in their return values. This means that to test these functions properly requires being able to look inside these return values, however all we have is testing for equality.

For Cmd and Sub testing for equality will usually suffice, so init, update and subscriptions functions can probably be tested adequately, but this will usually not be the case for view! We need the ability to test that a particular element is present, or has a particular attribute, or contains certain text. This leaves a gaping hole in testing. Selenium is a possibility, but that’s bad enough when you have control of the order in which elements and attributes are added to and removed from the DOM; with Elm there is a virtual-DOM differ doing this work for us, so who knows what Selenium will see as intermediate states? Creating your own transparent HTML structure which is then converted into Elm’s Html is a possible way round this, but so ugly.

Debugging

Although static typing means the compiler catches many errors, it is still easy to get Elm stuck in an infinite loop that chews through all available memory then crashes. The stack trace of an Elm out-of-memory crash just shows that the crash happened in Elm, and as Elm compiler does not produce readable JavaScript, Elm is essentially not step-debuggable. This is annoying, but not fatal if you run your code regularly.

So, is Elm ready for production?

Ease of Programming

I have no doubt that Elm is a great introduction to Functional Programming. I would highly recommend it for use in the classroom, and junior professional programmers of reasonable skill will have little trouble picking it up. Your intermediate “why can’t we just use Language X everywhere” types are likely to be more resistant, but their opinions don’t count.

The fact that certain things are missing compared to Haskell, such as do-notation, monads and typeclasses, actually helps its ease-of-use. Elm has a well-thought-out, practical feel.

Scalability

So many languages and frameworks make easy things easier and hard things harder. Elm is not quite one of these. As you scale up, you’ll benefit from pure functions and the testability and composability this provides. Elm is also mercifully short of ‘magic’; all interactions are clear in your code.

The downside is that you’ll end up with more and more boilerplate-like routing code that looks ugly and will need to be changed whenever the structure of your page changes (however useful CSS is for separating look and content, HTML defines the structure of both and Elm code must reflect this structure).

I would also be worried, if making a large single-page application (that is, an application is served up from a single request which contains many apparent “pages” of content), about all those signals being routed through the same signal. Would it not get very slow? I don’t know.

Testing

The most annoying aspect of Elm is that we cannot (without serious pain) escape Selenium tests, when Elm seems so close to allowing us to do so. However, the tests that you can do in Elm’s are as good as any other, but really better because your functions are pure so tests will not be dependent on the environment.

Mixability with JavaScript

I have not tested this, but from what I read this seems pretty good.

Conclusion

I am sure that Elm can work in production; it seems solid, easy to learn and lacking in surprises.

The cases in which it is likely to excel are for smallish components that have complicated internal interactions, such as diagram builders, graphical schedule makers or games. In these applications Elm’s simplicity, conciseness and ease of development will shine, while avoiding the problems of scalability and difficulty of testing the HTML.

It would probably disappoint as the sole client-side language for a large application.

Share

2 Comments

  1. Ian Rogers says:

    And the next thing to try is Kotlin in the browser… 🙂

  2. Tim Band says:

    I’ve just been trying to get Kotlin->JavaScript to work. I had some limited success (the command-line tools do basically work) but my conclusion is that, at the moment, Kotlin->JavaScript is only for people who need it so badly they are either prepared to pour days into getting it to work or who are prepared to work without proper tooling.

    I totally failed to get IntelliJ’s kotlin plugin to understand my source and its dependencies. I could not work out how to get gradle to put the JS libraries I was using somewhere I could get to them. Without a working build tool and IDE, this is already fringy stuff.

    The documentation for Kotlin/JavaScript is not usable. So you want to know how to interoperate with JavaScript? No problem! Just use nativeinvoke! The documentation for that says:

    Annotation nativeInvoke

    Calls to functions annotated by nativeInvoke will be translated to calls of receiver with the arguments provided for original call.

    Applicable to:

    member functions of native declarations
    non-member extension functions

    Example:

    native
    class A {
    nativeInvoke
    fun invoke(): String = noImpl

    nativeInvoke
    fun foo(a: Int): Int = noImpl
    }

    fun A.bar(a: String): Int = noImpl

    fun test(baz: A) {
    baz()
    baz.invoke()
    baz.foo(1)
    baz.bar(“str”)
    }

    Function test will be translated to:


    test: function (baz) {
    foo()
    foo()
    foo(1)
    foo(“str”)
    }

    Really? In what universe does that make a gram of sense?

    Getting anything more than hello world to work in Kotlin/JavaScript is an exercise in frustration.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*