“… for every complex problem, there’s a simple solution, and it’s wrong.” Umberto Eco
This tutorial is at an alpha stage. If you have any corrections, or find anything vague, please file an issue.
This is a ClojureScript tutorial for JavaScript developers. This tutorial does not presuppose any pre-existing knowledge of Clojure or Java. ClojureScript can stand on its own as an incredibly powerful language and ecosystem.
Through this tutorial, you will learn about ClojureScript’s syntax and idioms. But the larger purpose is to develop a rough mental map of the ClojureScript landscape: the tooling, the critical libraries, where to go for help. Our purpose is not to be exhaustive. There are more complete sources to read about Clojure’s syntax or the various Clojure web stacks. Our purpose is to begin with the assumption that you know nothing about ClojureScript, and get you to the point where you can begin to tackle the standard ClojureScript libraries.
By the end of this tutorial, you should be able to be productive using ClojureScript to manipulate the DOM and prepared to learn Reagent or Rum from the documentation that already exists.
For the motivation behind this tutorial, see here.
- A First Foray. An introduction to ClojureScript’s syntax, with plenty of exercise at the REPL.
- Hello World. The basics of compiling ClojureScript, running it in the browser, and using it to manipulate the DOM.
- Upgrading our toolchain. In this chapter, we set up and explore Leiningen and Figwheel, and build a larger example application. We start to see the limitations of manipulating the DOM directly as an application grows.
Why should JavaScript developers be interested in ClojureScript? With ES6/7 and a toolchain including Webpack or TypeScript, JavaScript has become quite a nice language to use. In many ways, the JavaScript world is implementing many of the ideas that had currency in the ClojureScript world for years. Libraries like Immutable.js provide immutable data structures, the React/Redux stack starkly demonstrate the benefits of functional programming to manage state, async/await provides a good experience for concurrent programming.
If the chief benefits of ClojureScript are a functional style of programming, immutable (and performant) data structures, and convenient tools for concurrent programming – why not just do it in JavaScript? Anyone who has spent hours configuring Webpack, tried to use Immutable.js in conjunction with other libraries, or simulated the experience of a more funcional, immutable language with libraries like Ramda.js will recognize that it is not a seamless, natural experience.
At present, many of the nicest features are purchased at the price of a more complex transpilation process. Want a nice type system similar to an ML or Haskell? Add Flow Types and set Babel up to strip them out. But do you want to be able to reference those types at runtime to validate data? Use tcomb and a Babel plugin. Heard of macros and want to try them? Add a compilation step using sweet.js. But at every step your transpilation process becomes more brittle, and the resulting experience, while beneficial, usually remains awkward.
Immutable.js gives you the benefit of persistent, immutable data structures. However, most libraries expect a regular JavaScript array, and the benefits Immutable.js brings are always attended by additional complexities and workarounds in practice.
The libraries and toolchain that has developed in the JavaScript ecosystem have brought many of the benefits of functional programming to the web world, especially on the front end. But anyone who has architected these systems know that to get these benefits require significant complexity in creating and maintaining the toolchain, and developers recognize that along with the benefits comes an ideal which falls quite short, and results in a number of workarounds. The benefits are real, but so are the inelegant hacks which we live with.
With ClojureScript, however, immutability is not bolted on to a mutable-first language, functional programming is not a second class citizen, and the toolchain is quite straighforward. ClojureScript is pragmatic; unlike some other compile-to-JavaScript languages, it is able to interoperate with JavaScript in a natural way, leveraging the full range of the JavaScript ecosystem. The fact that ClojureScript is a small community relative to JavaScript does not mean it has an impoverished ecosystem. As JavaScript’s ecosystem advances, ClojureScript is a direct beneficiary.
The JavaScript landscape is rapidly evolving, at both the library and language level. The ClojureScript language, on the other hand, is marked by its stability. The core of the language is quite small, and the core developers are reticent to add anything to the language core that could be implemented as a library. Because ClojureScript is a Lisp, syntax can be changed at the library level. Let’s consider a quick example.
One of the nice features of ES6 is the ability to use shorthand JavaScript property names. For example:
const a = 1;
const b = 2;
const c = 3;
const abc = {a, b, c};
abc => {a: 1, b: 2, c: 3}
JavaScript developers had to wait until this change was implemented in the language. But a ClojureScript developer can implement this quite simply in his or her own code base with a macro. One implementation is as follows:[fn:1]
(defmacro nmap [& xs]
(cons 'hash-map
(interleave (map (comp keyword name) xs) xs)))
(def abc
(let [a 1, b 2, c 3]
(nmap a b c)))
abc
=> {:c 3, :b 2, :a 1}
Don’t worry about the details. Macros are an advanced topic, and understanding them is not necessary to being productive in ClojureScript. The point is simply to see that ClojureScript can be quite stable, while almost endlessly flexible. ClojureScript is able to accomplish the feat of being both more stable and more flexible than JavaScript, without giving up access to the JavaScript ecosystem.
[fn:1] The example is jochenriekof’s.