ClojureScript: a frontend language designed for efficient state management

ClojureScript is Clojure programming language that compiles to JavaScript. It is a well designed, stable functional and dynamic language with a rich set of immutable, persistent data structures. The primary goal of the language is to make it easier to build complex and robust systems while keeping them simple. Even though Clojure is a backend language and it’s almost 10 years old already, its core ideas turned out to be a very good fit for modern frontend development. ClojureScript provides Clojure’s facilities and abstractions to JavaScript.

It runs in the browser, Node.js, Electron and React Native.

In this article I want to make an overview of Clojure’s core ideas and tools which are typically used for state management in modern web apps.

Data

Data transformation is at the core of every program. ClojureScript’s approach to programming is to build systems out of small and composable pure functions (that’s how the language itself is built). A typical web app would be a pipeline of data transformations through pure functions.

Data structures

a value

In ClojureScript everything is a value and values are immutable by default. Among having Numbers and Strings, ClojureScript provides a set of collection types. Some of them are: Vector, Map and Set.

[1 2 3 4 5]                  ;; Vector
{:fname "John" :lname "Doe"} ;; Map
#{1 2 3 4 5}                 ;; Set

The core library provides a huge number of functions to work with collections. In this example you can see how a threading macro (sometimes called “pipeline operator”) is used to perform a chain of transformations.

(->> (range 5 10)  ;; (5 6 7 8 9) List
(filter odd?) ;; (5 7 9)
(map inc) ;; (6 8 10)
(reduce +)) ;; 24

Nested updates is also easy to do. Works for both maps and vectors.

(assoc-in {:a {:b {:c 1}}} [:a :b :c] 2)       
;; ^__target ^__path ^__value
;; returns {:a {:b {:c 2}}}
(update-in {:a {:b {:c 1}}} [:a :b :c] inc)    
;; ^__target ^__path ^__updating function
;; returns {:a {:b {:c 2}}}

More fancier stuff, finding a diff between two values.

(clojure.data/diff {:a 1 :b {}} {:a 1 :b {:c 3}})
;; returns ({:b nil} {:b {:c 3}} {:a 1})
;; ^_in 1st ^_in 2nd ^__in both

Manipulating Clojure’s immutable and persistent data structures is a very performant process. Most of the time you don’t have to worry about it.

State

Because in ClojureScript values can not mutate, there exists a special reference type exclusively for managing shared, independent state.

an atom holding an immutable value

An Atom is a container which holds an immutable value. It is used to hold any kind of state e.g. client-side app state, DB connection, etc. Reading from an atom requires to “dereference” it, i.e. take the value by reference.

(def state (atom 0)) ;; an atom with value 0 bound to `state`
(deref state) ;; reads a value from the atom, returns 0
@state ;; same as `deref`, returns 0

Mutating state of an atom is also made explicit, you can not set it directly. Instead one should apply a function which will update a value inside of an atom.

a process of updating a value of an atom

swap! takes a value out of an atom, applies an updating function and sets returned value back.

(def state (atom 0))
@state ;; 0
(swap! state inc)
@state ;; 1

Atoms can have watch functions and validators. Because of this they are used as a primary reactive data storage in ClojureScript web apps. A watch function is called every time an atom gets updated and that’s where an app can be re-rendered. Also validator function can be used to validate an updating state against a schema, which is useful.

Dispatching actions

Among all available options for runtime polymorphism in Clojure, multimethods are particularly interesting as a replacement for dispatcher component in architectures built around unidirectional data flow.

A multimethod is a combination a dispatch function and methods which are bound to possible dispatch values. A dispatch value is a result of applying arguments to dispatch function.

When a multimethod is called, dispatch function is called first to produce a dispatch value out of arguments. The dispatch value is used to find out which method should be called. A method is then called with all arguments.

;; Definition 
(defmulti say (fn [entity] (:who entity)))
;;fn name_^ ^__dispatch fn
(defmethod say :cat [] "Meow!")
;; fn name_^ ^__dispatch value
(defmethod say :dog [] "Bark!")
(defmethod say :cow [] "Moo!")

;; Usage
(say {:who :cat}) ;; "Meow!"
(say {:who :dog}) ;; "Bark!"
(say {:who :cow}) ;; "Moo!"

This method of polymorphism is often used to route actions in ClojureScript web apps. A method for a particular action type is a pure function from old state to a new one.

Here’s an example of using multimethods and atom for basic state management.

;; App state
(def state (atom 0))
;; Watch function
(add-watch state :watcher (fn [_ _ _ new-state]
(render new-state)))
;; Action handlers
(defmulti handle (fn [action] action))
(defmethod handle :inc [_ state-value] (inc state-value))
(defmethod handle :dec [_ state-value] (dec state-value))
;; Action dispatch function
(defn dispatch [action]
(swap! state #(handle action %)))
;; Usage
(dispatch :inc)
(dispatch :dec)
(dispatch :dec)
@state ;; -1

Orchestrating async data flow

Sometimes there’s a need to handle a complex asynchronous control flow, e.g. multiple network requests in series or handle communication between concurrent systems. Callbacks are going nowhere as an app grows.

Coroutines is one way to avoid this complexity. Clojure’s core.async library provide Go-style facilities for building concurrent systems via channels and go blocks. A go block can suspend execution, while waiting for incoming message, which allows to write asynchronous code in synchronous manner. Since JavaScript is single-threaded, a go block in ClojureScript is just a state machine (or generator function) which runs separately from main program flow. Independent processes communicate via channels. A channel can be passed around as a usual value. This makes it possible to establish concurrent communication between separate modules.

communication between processes via channel

Here’s an example of how an app can communicate with network API layer while being completely separate pieces of code.

;; Channels to communicate with network API process
(def api (chan))
(def api-success (chan))
(def api-error (chan))
;; Network API process
(go
(while true
(fetch (<! api)
#(>! api-success %)
#(>! api-error %))))
;; Consuming network API process
(go
(while true
(let [[value channel] (alts! [api-success api-error])]
(case channel
api-success (render-data value)
api-error (render-error value)))))
;; Initializing network API process
(put! api "uid")

core.async is often used in ClojureScript apps for communication between separate modules like components and network or storage layers. It’s also used instead of multimethods for handling actions, when appropriate. The closest implementation in JavaScript world would be redux-saga library.

Transducers

Data processing can be slow as amount of it grows. Since data transformation is what most apps doing most of the time, Clojure introduced a concept of transducers, lightweight composable data transformation functions. Transducers is a good way of achieving performance when processing a large collections of data. At first glance they are not that different from usual map, filter, etc. functions, but what makes them performant is how they process data.

Instead of producing an intermediate collection after every transformation in a chain, a transducer function pipes every value of a collection one by one through a chain of transformations. This results in a much better performance when used on large amounts of data.

a transducer function composed out of 3 other transducers

Another interesting characteristic of transducers is composability. Transducers compose via usual function composition, which allows to have a set of reusable transformations.

;; Define 3 transducers
(def trimmer (map trim))
(def not-empty (filter #(not (empty? %))))
(def tokenize (map #(split % " ")))
;; Compose into another transducer
(def text->tokens (comp trimmer not-empty tokenize))
;; Apply transducer
(transduce text->tokens conj [" " "fname lname "])
;; returns ("fname" "lname")

Transducers can be also used with core.async channels. This is useful for example when you need to transform server response before data is getting into the state of the app. If you want to learn more about transducers, read the article “Understanding Transducers in JavaScript” which I’ve posted a while ago.

Conclusion

To sum up this overview I’d say that Clojure as a language and its core library provides an interesting and powerful tools for building web apps. Whether it is a complex system or simple web app. Even though syntax of the language is very concise and hard to read for beginners, its functional nature and solid design principles allows building large, robust and maintainable systems while keeping simplicity at their core.

If you want to know more about ClojureScript, check out the official website for reference and guides: clojurescript.org