Elm vs ClojureScript, a first encounter



Recently I have been gathering information about what makes for a happy programming environment. The Elm language kept popping up as a leader in most of the metrics I am interested in. So on the weekend I gave Elm a try and was pleasantly surprised. Elm solves practical problems in a very elegant way, and the compiler is very friendly. I coded up a basic counter that represents numbers as n sided polygons to get a feel for the language.





Below are my notes on my first impressions compared to ClojureScript. Elm is still new to me, so apologies in advance if I make some false assumptions, and please add corrective comments.


  • Elm wins


  • Elm error messages are absolutely fantastic. The tone used is friendly and inviting. Plenty of context is provided. Messages never exceed a page, so I don't have to scroll around. Whitespace and formatting is used to delineate errors and separate information about types, code, and suggestions. The suggestions are surprisingly accurate. Often the suggestion is exactly what needs to be fixed, and when it isn't, the suggestion still provides good search terms to research in the documentation. All of these features conspire to allow me to very quickly identify the relevant information and keep productive.
    There are some examples of error reporting here: http://elm-lang.org/blog/compiler-errors-for-humans. You really have to try it to appreciate it, the examples don't convey just how effective the help is.

  • Pattern matching is strong with Elm. We get destructuring and case. Case is like core.match, but built into the language, idiomatic, and type aware. This encourages clear and concise function.
  • Elm has a literal assoc syntax.
    { bill | name = "Nye" }
    is equivalent to
    (assoc bill :name "Nye")
    Data structure literals assist thinking. Hashmaps and their ilk are a programmers bread and butter. Having a dedicated notation really helps focus in on the core abstraction. I really like having the update notation.

  • SVG works! Elm is not just wrapping the basics, it provides a comprehensive solution. It can pretty much do everything as far as I can see. The combination of FRP with SVG is powerful and fun to play with. SVG lends itself to the data-to-view philosophy in ways that canvas cannot. Having checked SVG is pretty handy, no more typos.

  • Excellent documentation guides and references. Well written, complete, well presented. The introduction is very pleasant to read and within a couple of hours I was equipped to code and experiment.
    https://guide.elm-lang.org

  • Type inference works great. I didn’t have to specify any types to do what I wanted to. There was some minor casting to be done.

  • The View Model Update separation is clear and clean.

  • While learning Elm I never felt stuck or frustrated. I was able to build a small project after reading half the introduction. Closest to stuck was getting lost in syntax… breaking out functions identified the real issues.

  • Imports are much cleaner than namespaces. Git based modules has appeal. Elm's module system is interesting. Elm does have strict semantic versioning, which is intriguing. Perhaps it will bring sanity to dependencies. I don't expect to run into the weird dependency conflicts I see in Clojure. Compiler verified non-breakage? Yes please!
  • ClojureScript wins


  • Code reloading (figwheel/test-refresh/ring-reload) is important to my workflow, and ClojureScript has more mature tooling for that. The author of Elm is thinking about how best to do code reload: https://github.com/elm-lang/elm-reactor/issues/193

  • Syntax. Lisp's lack of syntax makes for some useful editor optimizations. For instance deleting/moving/copying entire functions or blocks of code is very convenient using paredit because they are fully enclosed in parenthesis.
    The syntax rules detracted slightly from the otherwise awesome error messages because I sometimes confused call signature errors (like leaving out a required empty list) with language features (such as creating ranges using [1..x]). On the other hand, the lack of parenthesis does have a pleasing minimalistic aesthetic. I expect to appreciate it more over time.

  • Editor support appears to be wide for Elm, with many plugins, but no where near as feature rich as Clojure. The Emacs plugin did not give a great experience, it appeared to only give syntax highlighting. Indentation never lined up to where I expected it to. The Light Table plugin suffered the same indentation woes. It is not clear to me how to get REPL interaction with either of them. However, this really was not a big deal. I was fine editing the code without much editor support thanks to the excellent documentation and error messages. I'm sure with a little elbow grease I can adapt my editor. If anything I would say that Clojure editor integration is way harder, but I've put this as a slight advantage to Clojure just because the integrations are quite mature and can do all sorts of whacky stuff.

  • Front to back. Clojure has an advantage in being viable for all my coding needs. Elm appears to be restricted to the front end. I imagine this will change (maybe it already does target NodeJS???). Is it a general purpose language? Given it's heritage I think Elm will be general purpose one day if it isn't already.
  • The package manager is not as powerful as Leiningen/Boot, there is no .m2 or plugins.



Overall Elm has some clear advantages, and I will definitely be using it for my next personal project (when I can dream something up!) I hope the Elm ecosystem continues to grow rapidly.

For reference, here is the code for creating an SVG polygon path with n sides:

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Dec ] [ text "-" ]
    , div [] [ text (toString model) ]
    , svg [ width "400", height "400", viewBox "0 0 100 100" ]
        (numeral model)
    , button [ onClick Inc ] [ text "+"]]

numeral x =
  let
    dphi = 2.0 * pi / (toFloat x)
    phis = List.map (\i -> (toFloat i) * dphi) [1..x]
    points = List.map (\phi -> (toString ((cos phi) * 20.0 + 50.0))
                           ++ " "
                           ++ (toString ((sin phi) * 20.0 + 50.0))) phis
    p = "M " ++ (List.foldr (++) "" (List.intersperse " L " points)) ++ " z"
  in
    if x == 0 then
      [ circle [ fill "none", stroke "black", r "20", cx "50", cy "50" ] []]
    else if x == 1 then
      [ path [ stroke "black", d "M 50 30, L 50 70"] []]
    else
      [ path [ fill "green", strokeWidth "5", stroke "black", d p] []]

11 comments:

  1. Elm Light Table plugin author here ! (Fun fact: The elm plugin for Light Table is mostly written in cljs). Anyways I'd like to comment on a few things that might be useful if you decide to give Elm with Light Table another go in the future:
    - Indentation, yeah it's not great. But the thing is, in the future Elm formatting will be settled once and for all by https://github.com/avh4/elm-format (much like gofmt). The plugin already supports the alpha version of elm-format - Ref: https://rundis.gitbooks.io/elm-light-guide/content/elm-format.html . It's not super polished as elm-format needs to add support for keeping track of cursor position (:
    - Repl: Did you see the this section on the plugin user manual: https://rundis.gitbooks.io/elm-light-guide/content/elm-repl.html ? Elm repl support is nowhere near the support you have in Clojure and ClojureScript, but for non ui type of exploration it works ok.

    I also miss paredit (/or parinfer) ! Hoping to remedy some of that longing once a proper Elm AST becomes available in the future

    ReplyDelete
    Replies
    1. Hi Magnus, thank you very much for pointing out the manual; I missed that when using the LT plugin installer. The documentation is excellent, and I can see that your plugin has many great features that I just was not aware of... I will return to it this weekend.

      Delete
  2. cljs user here. I always feel it painful to make side effects in Elm. I'm good with the pure render part, not I can't get along well with the Model, the ajax things, that's hard.

    ReplyDelete
    Replies
    1. Ah interesting; I'll look into that some more

      Delete
    2. Yup. Making side effects in Elm is harder than Clojure or ClojureScript if you like. In cljs it's a breeze to just invoke any ol' js, ffi is virtually seamless. But that comes with a price too. There is no promise of no runtime exceptions in cljs nulls or undefined are just around the corner, you are encouraged to be careful about sideeffects in cljs but it's real easy to step out of line and fire the missiles. In elm that's not an option. In your program everything is a pure function, only the Elm runtime is allowed to make side effects. It's tradeoffs, but I'm starting to see a lot of benefits of the constraints (testability, predictability, maintainability, reaoning etc), but I can totally symphatize with finding it constraining and at times frustrating. I guess the only way to figure out if it's worth it or not is giving it a fair go :-)

      Delete
  3. Hey there Tj! Everyone's a winner baby, that's no lie...

    ReplyDelete
  4. I think the biggest argument in favor of cljs for me is the interop. Cljs has great interop with the JS ecosystem and Elm has terrible interop (ports aren't a good solution imo, and all the Native stuff is kind of kept behind closed doors).

    ReplyDelete
  5. I noticed there are a few packages for SVG work in Elm. Were you referring to one of them?

    ReplyDelete
    Replies
    1. I was only relying on the HTML package, which supported SVG elements well.

      Delete
  6. I actually used Elm for a lil' while and totally loved it, but my problem is the same as 题叶, side effects was a bit hard, and now looking into cljs, to see what it has to offer, i've always been interested in LISP dialects, thats why i decided to give cljs a shot, let's see how it turns out.

    ReplyDelete
  7. After trying Elm and getting rung up on learning syntax and types, the next major pain is communicating to a server. In a transaction based app, the "one source of truth" has to be the database, but Elm reserves this to itself. While the architecture is clearly defined and R. Feldman sample code is great, the JSON pain is too great for my application.

    ReplyDelete