Hacker News new | ask | show | jobs
by hellofunk 3247 days ago
Similarities:

1) Re-frame and Elm are both backed by a virtual dom (Re-frame leverages React behind the scenes, Elm uses virtual-dom)

2) Both impose a single app state

3) Both require user events and outgoing state mutation to be explicitly defined somewhere outside of the context of a view

4) Both operate on a sort of "game loop" style of processing events and calling view functions

5) All data is immutable in both languages (though Clojurescript provides explicit mutable structures if desired, which require special syntax so any mutation is evident)

Differences:

1) Elm's model is pure FP -- data is passed to a function, and then it calls other functions. An individual view function in Elm cannot independently subscribe to any kind of data event, either a state change or a socket message, etc; it must receive the data it needs as an argument to the function only. Depending on who you talk to, this is either limiting or properly restrictive. It does mean that a branch of your Elm user interface should generally correspond to a single branch of your app state, or that view functions in complex interfaces must take quite a lot of arguments; this is a pattern for which re-frame explicitly offers a workaround (though you can also do it the Elm way in re-frame if you want). In general, these differences lead to more re-usable components in re-frame than in Elm.

2) Elm has no real asynchronous library for high-level concurrency. Most Clojurescript projects (at least large SPAs) often leverage core.async as an abstraction for large UIs that handle many independent processes simultaneously. Core.async makes clojurescript particularly versatile for accomplishing things in a single-threaded environment as if it were modelled with multiple threads.

3) Elm has its own compilation story that is separate from the JS ecosystem. The Elm devs are working on dead code elimination. Clojurescript projects are all built on Closure, which provides DCE, compression, and global inlining which can provide some projects notable speedups. The Closure libraries also give Clojurescript cross-browser abstractions for hundreds of common tasks in front-end development that have been battle-tested by Google in all of their web services, so it can greatly reduce development and debugging time for complex apps that run everywhere.

4) Elm controls JS interop much differently than Clojurescript. The tradeoff is that in Clojurescript you will inevitably spend more time debugging runtime errors, while in Elm you will spend more time developing your JS interop code and testing cross-browser compatibility.

5) There are many UI libraries available for re-frame because it can wrap existing JS tools (including wrapping UI libraries built for React). There are fewer for Elm, and it's more common to roll your own UI in Elm. There are pros and cons here. I've worked on two large projects in Clojurescript, one which was all custom UI components and another that leveraged polished libraries in the wild. The former took much much longer to make but looked more unique. Having the choice to go either way is a big benefit since project goals widely vary. If you are primarily a developer/coder and not a web/graphic designer, you may be frustrated at UI design in Elm. If you work with a designer, this is not a problem. But if you work alone, having access to a lot of UI tools can free up your time and efforts quite a bit, and Clojurescript has an edge here.

6) Types: Elm has static types, while Clojurescript offers clojure.spec (which is optional though widely used). Spec is a runtime contract system so you can guarantee that all args passed to functions or setup in data structures meet specified criteria; not just types of the args, but also any other predicates, such as a valid range for an integer, etc. For example, an Elm union type would be a Clojure set, where each element could be a specific data structure with other specs attached to it. However, Elm has proper static types, which are caught at compile time, not runtime. There are benefits of both styles. If you want to tightly catch very specific data aberrations that flow through an app, clojure.spec is easy to use for that purpose; but if you want more general checks before the program runs, Elm would be preferable.

7) Performance: All of Clojurescript libraries that wrap React (including Re-frame, vanilla Reagent, and Om) offer an interesting runtime optimization that I've not seen in other languages, and is not available in React itself. If the data that a view function requires (either via its arguments or subscription) has not changed from the prior render frame, then the entire view function is skipped without running, since its output would not be any different. What this means is vast sections of your app's code don't even need to run on each render frame, and this is not trivial. In a small app, it would make no difference, but in complex SPAs this can be very significant. Elm offers a library, Html.Lazy, that attempts something similar, though my impression is that most Elm projects do not use it since it can be tricky to explicitly add this behavior; in Clojurescript, it is built in automatically. In Re-frame, they go an extra step by de-duplicating any queries or subscriptions so that multiple views which use the same data context do not query, fetch or calculate the required data more than once, which is then passed to all requested functions. If you are working on an app that processes lots of data frequently, this can be the single selling point of using Clojurescript, as it frees up the CPU to deal with only those things that are guaranteed to require processing.

Clojure's syntax is very tight and concise, while Elm's language is elegant but more verbose. I have found that there is approximately a 2.5X increase in code size in Elm when trying the same ideas out in both languages.

All other things being equal, you can probably get a re-frame app going very quickly; if you need to prototype something or get to an MVP as soon as possible, it is really hard to beat Clojurescript compared to any other compile-to-JS language. If however you are going for application purity and a reduction in runtime debugging, Elm (or Purescript or some other statically-typed languages) would be a better fit.

Ultimately, Clojurescript is a general purpose language, while Elm has a very specific use-case; if you fit into the Elm model, it can be nice. If you need to reach outside that model, it is a challenge.

2 comments

Worth mentioning that Clojure(Script) comes from a different school of thought (Lisps) and has its own approach to development called REPL driven development. It allows you to experiment a lot and fail fast.

Large projects are absolutely doable but require more discipline and experience with the language from developer. For example you can start with a crude prototype and introduce clojure.spec later. When you do this is entirely your choice while in Elm you just have to write function signatures and types/structures definitions from the beginning.

ClojureScript has a very easy interop with JavaScript. Actually this is one of design goals. Consuming JavaScript library is super easy. For example most of virtual DOM libraries are built upon React. This is good because writing performant virtual DOM with older browsers support isn't an easy task.

The toolchain has code splitting and dead code elimination. It had it long before Webpack and can even optimize imported JS libraries code. As far as I know Elm still can't do it.

Another good thing is you can use Clojure(Script) both on the client and the server. This allows you to nearly skip data serialization/deserialization using powerful "transit" library. Actually you can use transit on server with several other languages. So it's not a lock in but it's much smoother with Clojure.

The libraries ecosystem is rich. For example Clojure(Script) has a mature WebSockets library (it's both server-side and front-end) with any kind of fallback you can imagine.

I agree with much of your points but a few things to add clarification:

While lisps do traditionally provide repl-driven development and you can do this in Clojure, most Clojure devs I know are not actively working right at the repl. If you do like some aspects of repl-like interactivity, Elm does have elm-reactor which has some similarities to Clojurescript's figwheel, though not quite as mature or comprehensive.

Aside from the repl, you are right that you can get a proof-of-concept going in Clojurescript extremely fast so if you want to see if an idea succeeds or fails before investing lots of time, you can't beat Clojurescript.

The Elm toolchain is slowly evolving to include things like dead code elimination (which it doesn't have yet but probably will in the near future). However, I think it will be a long time (if ever) that Elm will support global inlining, automatic variable renaming and some other optimizations that come for free with the Google Closure compiler support. The other important difference is that as the Google Closure compiler adds features or further improves its optimizations, these are mature and widely used and immediately available for free without much (if any) extra work on the Clojurescript team. It's a philosophical difference that the Elm devs prefer to solve these problems on their own rather than leverage existing tools in the industry, but since the Elm dev community is small (much of it is only one person), this means that lots of time must be spent on these solutions at the expense of features more specific to the Elm language.

Also I think you are right that Elm as a language is a little easier to learn than Clojurescript (though to someone new to FP, both Elm/Haskell style syntax and Lisp syntax might be equally bizarre). But I think that ease is made up for by some of the concepts in Elm that can be difficult to learn (decoding, Tasks, ports, etc), so it's probably a net flat difference on the learning curves between the two systems.

Thanks for clarifications. I actually pay some attention to Elm evolution and check the official blog from time to time. I was much more interested in Elm several years ago and I even tried to get a job at Prezi (Evan Czaplicki was still working there at that time). In the end I like ClojureScript more. It has a good balance of agility and guarantees. It's more generic. REPL enables exploratory programming and live coding. Btw ClojureScript developers I know do use REPL.

I probably wasn't clear enough but I don't think Elm is easier to learn. It's quite the opposite. The good thing about Elm is great default libraries for SPA development (or isolated widgets) and great toolchain. You just use what's given and it works perfectly fine. In the end getting productive fast is easier with Elm than with ClojureScript. And it's easy to do all by yourself. Everything is pretty strict and official site has a lot of up to date tutorials. I also like that Evan values simplicity and Elm gets better and better in this regard.

Every language has its place. ClojureScript is my personal preference. It's just a good fit for things I like doing.

I believe you can use Google Closure Compiler on Elm JS files with great effect the same as Leiningen uses it on ClojureScript JS files. (Although most people probably use uglifyjs instead.)

But of course ClojureScript's JS is generated carefully so as to be better optimizable by Google Closure Compiler, and that's not the case with Elm. IIRC, in version 0.19 Elm will change it's JS so that it's better optimizable by uglifyjs.

> I believe you can use Google Closure Compiler on Elm JS files with great effect

No, you can't use the advanced optimizations from Closure compiler in Elm. This is unlikely to change any time soon because Elm's compiler emits JS that directly violates one of the restrictions that Closure requires for advanced optimizations, namely the referencing of field names as a string.

Good to know, thanks!
Great explanation, thank you !