As I have taken the summer off and have a bit of time on my hands, I thought an interesting way to compare would be to write the same program in both Elm and ClojureScript, so I wrote a simulation of the K-means clustering algorithm in both Elm and ClojureScript.
It is quick to get started with Elm as it has a Windows/Mac/NPM installer that installs everything you need. ClojureScript is a more work as you need to install Java first and then have a coffee as Maven downloads the universe.
Next, you need to pick your libraries, in this scenario one for rendering SVG. This is straightforward in Elm as there is a standard SVG library. The other core Elm libraries needed are included by default. For ClojureScript, I went with reagent (which seems to be the most popular of the React wrapper libraries). Happily, reagent can render SVG tags directly without requiring a SVG library. I also ended up using
core.async to simulate a ticker (more on that below).
The big change moving from ClojureScript to Elm is getting your head around using the strong types and the compiler. It takes a bit of a mental shift to get used to working with the (friendly) compiler versus the more dynamic REPL and experimentation type workflow typical with ClojureScript.
One handy shortcut I used was:
elm-make --warn Main.elm to generate the type signatures as you go.
Elm does come with a REPL, but I don’t find myself using it frequently. The ClojureScript REPL works pretty well, although I don’t use it quite as intensively as when doing pure Clojure development. Setting up the ClojureScript REPL can be a bit tricky with vim.
For both implementations, I first defined a model (in Elm) and an app-state (in ClojureScript). In Elm this is codified by the Elm architecture with its Model-Update-View pattern. The pattern of having a global app-state is a common convention in ClojureScript react style development. The re-frame framework is also available if you want to have an Elm architecture like app structure for ClojureScript. The Elm architecture can feel a little verbose for a small project but pays off on bigger projects as an aid to understandability and structure.
One initial hurdle was how to generate a set of random points to cluster. I did this in Clojure by calling
As Elm is a pure functional language generating random numbers is a little less conventional. First, you define a generator specifying the type of random data you need. You then ask the Elm runtime (via a side effect command) to generate the random data for you.
To animate the progress of the algorithm, I required a tick event every second to progress the algorithm to the next iteration. In Elm there is a Time library with a straight forward API and a standard mechanism, subscriptions to configure this:
setTimeout function and using a
core.async channel. This is trivial to do but needs some wider knowledge of the Clojure ecosystem.
One area where Clojure does excel is its fantastic standard library. I enjoyed implementing the actual algorithm using the
What to use?
Elm’s strong point is that it tries to be easy to use in its tooling and developer experience. Clojure makes different trades off (see Rich Hickey simple made easy talk) and due to how ClojureScript evolved from Clojure the ClojureScript stack is inherently more complex. Don’t get me wrong, the ClojureScript team have done an amazing job and are continually improving ClojureScript. Once you are familiar with the ClojureScript ecosystem you can be very productive. As a newbie or occasional user, it can be a bit daunting. ClojureScript does not have a prescriptive architecture like Elm. This makes it slower to get started with but allows you more flexibility to evolve an architecture that better fit the problem.
So what to use? As usual, this depends on social and external factors, not only the technology. If I was on a team with existing Clojure skills and systems, ClojureScript is an obvious choice. On a team interested in functional programming but without any existing Clojure skills and systems, Elm may be a better fit. There are situations where React/Redux and an es6/typescript transpiler is a good choice depending on the team’s skillset, existing libraries that you may want to leverage and the problem you are solving.