Comparing ClojureScript and Elm

Last year I spent some time experimenting with Elm, a Haskell-like language that compiles to JavaScript. After a bit of a break from web development, I started to look at Elm again. I was curious to see how it compared to ClojureScript the other compile to JavaScript language that I have used.

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.

kmeans

You can play around with the Elm version here and the ClojureScript version here.

Getting started

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).

Development experience

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.

Happily, once you get your code to compile it does tend to work as expected in the browser. With ClojureScript on the other hand, I did get a pretty hard to grok JavaScript error which took a while to figure out.

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.

Elm has an integrated time traveling debugger that you can use in the browser by appending ?Debug to the URL: localhost:8000/Main.elm?Debug. A similar tool re-frisk is available for ClojureScript.

Implementation

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 rand-int.

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:

In ClojureScript, this can be done by combining a call to the native JavaScriptsetTimeout 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 clojure.core functions.

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.

Advertisements

Adding Clojure unit tests results to Bamboo Continuous Integration server

We have started to use Bamboo for building our clojure project. It requires a little bit of tweaking to your project and the bamboo project setup to integrate unit test results fully.

First add the test2junit plugin to your leiningein project.clj file:

:plugins [[test2junit "1.1.0”]]

Then configure Bamboo to run the test2junit task:

Configuring the tests to run

Configuring the tests to run

Finally you need to setup a JUnit Parser task to pickup the results.

Parsing the test results

Parsing the test results

By default this path should work:

test2junit/xml/*.xml

Detecting web applications that aren’t converting with Riemann

We have been playing around with Riemann at uSwitch to do some of our monitoring. One of the core metrics we track is the number of customers who are currently converting on our website. If this number drops to zero it usually indicates something is broken. Each time a customer converts an event is sent to our Riemann server.

I added a stream to our riemann config to count recent conversions, create a new summary event and notify us whenever that number drops to zero.

  (streams
    (where (and (service"app") (tagged"conversion"))
      (moving-time-window
        30
        (smap (fn [events]
                (let [conversion-count (count events)]
                   {:service"app-conversions"
                    :time (unix-time)
                    :metric conversion-count
                    :state (if (> conversion-count 0) "ok" "warning")
                    :description"Conversions in the last 30 seconds"
                    :ttl 30}))
                 index
                 (changed-state
                  (fn [event]
                    (warn "Conversion state has changed to: " event)))))))

Sadly this didn’t work as I expected! If there are no conversions than no events are sent to Riemann and the moving-time-window code block is not executed. This is discussed further here.

You can work around this by however by using expired events. Events in Riemann have a time to live (TTL) associated with them. If an updated event is not received within the TTL the event will be expired – indicating in this case that no further conversion have taken place since it was last fired. You can add a stream to catch this event expiration and notify whoever is interested.

Just like above we add a stream to sum recent conversions and create a summary event:

  (streams
   (where (and (service "app") (tagged "conversion"))
          (moving-time-window
           30
           (smap (fn [events]
                   (let [conversion-count (count events)]
                     {:service "app-conversions"
                      :time (unix-time)
                      :metric conversion-count
                      :state "ok"
                      :description "Conversions in the last 30 seconds"
                      :ttl 30}))
                 index
                 (changed :state
                          (fn [event]
                            (info "notify that conversions are happening again" event)))))))

Then we add a stream that waits for the summary event to expire:

  (streams
   (expired
    (where (service "app-conversions")
           (with {:state "warning"
                  :metric 0
                  :ttl 30
                  :description "No conversions in the last 30 seconds"}
                 (fn [event]
                   (index event)
                    (warn "Notify about no conversions" event)))))))

The above config is on github.