Recently, I started a new role as a Software Engineer at Reify Health. Unlike any other role that I have had, a huge part of our product stack is written in Clojure and ClojureScript. Thankfully, I have spent my last few years as a JS/TS dev working with React and as a result, have employed and appreciated a very functional style. ClojureScript has unlocked whole new levels of productivity in JavaScript and React. Particularly, it has shut the door on patterns that are pain points and allowed for new paradigms that JavaScript did not elegantly allow one to express.
Unlike JS, Clojure is a LISP and has much less syntax. Want to declare a function?
(defn add-two [x y](+ x y))
vs.
function addTwo(x, y) {return x + y;}// or...const alsoAddTwo = (x, y) => x + y;
By little syntax, I don't mean the code-golf of how many characters it takes to
express an idea. I mean the number of symbols that it takes to express the
language. In Clojure, the syntax is largely parenthesis and functions. There are
also @
React has a lot of ways of managing state. Popular ones include useState
useReducer
useContext
mobx
redux
reagent
r/atoms
import {useContext, useState, createContext} from 'react'const MyContext = createContext()const ContextProvider = ({children}) => {const [clicks, clicksSet] = useState(0);return (<MyContext.Provider value={{clicks, clicksSet}}>{children}</MyContext.Provider>);}const countingComponent = () => {const [clicks, clickSet] = useContext(MyContext);return (<div> The atom has <code>clicks</code> has value {clicks}.<input type="button" value="Click me!" onClick={()=> clicksSet((c) => c+1)}></div>);}
vs.
(ns clicks.core(:require [reagent.core :as r]))(def clicks (r/atom 0))(defn counting-component [][:div"The atom " [:code "clicks"] " has value: "@clicks ". "[:input {:type "button" :value "Click me!":on-click #(swap! clicks inc)}]])
While trivial examples, what is demonstrated here is that one does not need to declare a context at or worry about context HOCs when working in ClojureScript. If one does not like the ClojureScript r/atoms, they do not have to use them as ClojureScript has the ability to interop with JavaScript code. Use hooks, use your favorite component libraries!
ClojureScript Reagent apps often leverage two libraries to manage state on the front end: Fulcro and Re-frame. We will focus on the re-frame method of doing things since it is similar to a popular, yet commonly maligned pattern used in the React universe: flux through Redux.
Why do folks use things like Redux? Redux creates a global state store, lets users dispatch actions to update it, and create subscriptions into the data store to read out of it. State is updated by the reducer returning a new object with the updated state. A shallow comparison is done on the updated values, so it must be done immutably. Instead of managing your application state all over the place in different contexts and/or components, developers just manage it all at the top. This is not far off from how many "traditional" applications have been designed for years.
Web applications do not have to be some abomination. One can model them like any other system: Views that are derived from some sort of database. APIs often do this through RESTful endpoints that sit on top of some database. Reads and writes into that database are oftentimes done predictably at different layers of the application. There is no reason why one can not model front ends of an application to look the same way. Instead of an endpoint sitting atop a database, it is a React component with a selector that sits on top of a redux store. Instead of writing SQL, one would write the path to the useful data in JSON. Like SQL does with views, one can even create more pointed (and often performant) queries through using selectors. In redux-land, this is done by taking advantage of memoization.
While hooks have made the boilerplate less stressful, they do pack a lot into a component: Particularly the selector and the dispatch. One can organize all of these things into named queries, but that is something that is on the user and requires even more boilerplate. The typical process in a JS app is to create the reducers to represent the state, put those into a redux provider, create actions that update the reducer, and create components that allow for the user to read from the store as well as dispatch actions. I'd give a full example of redux boilerplate, but most readers (myself included) would probably get bored and not read the other points 😅. Take Twitter's word for it.
Most modern front end web applications also require some sort of network
communication with a backend server. Often, Redux users just accomplish this by
using something like Redux Thunk. This
allows async logic to exist inside of dispatched actions. This is a very nice
feature and why I still prefer redux for API requests over the context API,
which would require one to keep passing the dispatch
useReducer
Why is it that Clojure Developers can follow almost this same pattern to a tee,
but not have the same complaints? The smaller language footprint and stronger
resources at the language level make it easier to do write expressive code and
reduce boilerplate. For example, actions in Redux give a named type
switch
:hello
::hello
Re-frame doesn't use actions, it uses something called effects. Effects, like
actions, are dispatched by components (or other effects), and update the state.
As opposed to creating an action that gets dispatched to a reducer, the effect
just goes ahead and updates the db directly. This is accomplished by returning a
new version of the db with the updated values. Redux is technically doing this
too (that is what one is doing with default: return state
http-fx
http-fx
Reframe uses subscriptions to fish data out of the database. While re-frame does support reading out of the database and shipping the result to the component, it also provides a model for users to create complicated compounded subscriptions in a performant way using materialized views. This is similar to application developers creating materialized views on top of larger data sets to achieve better performance and following the model of writing a web application like any traditional application.
It is worth adding that similar to redux, there is one big global state store in
re-frame. One declares this upfront by creating the initial app-db, which is
just one big reagent/atom. Like Redux, one may put default state values in
there. One may even take advantage of libraries like datascript
What does all this look like? How can it have less boilerplate?
(ns example.core(:require[re-frame.core :as rf]));; effect to set a value in the db(rf/reg-event-db:set-value(fn [db [_ v]](assoc db:value v)));; Subsription to read value out of the db(rf/reg-sub:read-name(fn [db](:value db)));; use in a component(defn my-component [](let [value @(rf/subscribe [:read-name])][:div[:div (str "My Value is " value)][:button {:on-click #((rf/dispatch [:set-value (inc value)]))} "increase"]]))
That's only the beginning with what re-frame can give one as a developer. It has great devtools with re-frame-10x, a rock solid stable API, and fantastic docs!
I first fell in love with immutability and functional programming when working
with Scala. It was fascinating to me that one could just have an application be
an expression based on top of data. By avoiding mutation wherever possible, even
completely, programs were so much easier to reason with. React allows for one to
exercise this passion on the front end. However, JavaScript in a way makes you
do this with a hand behind you back. It only has two data types that have first
class immutability: objects and arrays. Arguably, the first class immutability
also really only exists in a subset of features such as spread syntax and
Array's .slice
Set
ClojureScript, though implementing the Clojure's core library, gives folks access to all of Clojure's data structures. The collections and libraries around them are especially powerful. The key is that under the hood, they are implemented with only immutability in mind using techniques such as structural sharing, making them far more performant than just doing the operations on objects. There are libraries such as immutablejs that allow for folks to get a lot of these tools as well, but having it native to the language is next level in terms of support and documentation. Lets start with a trivial example such as updating a nested value in an object immutably.
const obj = { i: { j: { k: 1 } } };// update k by one immutablyconst newObj = { ...obj, i: { ...obj.i, j: { ...obj.i.j, k: obj.i.j.k + 1 } } };
(def obj {:i {:j {:k 1}}}); update k by one immutably(def new-obj (update-in obj [:i :j :k] inc))
Despite Clojure's somewhat deserved reputation of having a lot of parentheses,
the Clojure update is a lot simpler! This is just the beginning. assoc
Object.assign
Interestingly, React's original author Jordan Walke must have somewhat agreed with the above because after React, he went on to create a functional immutability first language called ReasonML that also compiles down to JavaScript. While ReasonML is by all accounts fantastic, it is not the subject to which I am writing 😉.
At Reactathon in 2019, I recall somebody calling JSX: the "Gateway Drug to React" (Can't recall who said it, and don't have 5 hours to re-watch it all). It makes React easy to pick up for someone that knows a little JavaScript and HTML. However, HTML is too declarative of an S-expression to be efficient in constructing the DOM. In fact, after working with ClojureScript, I do not think JavaScript even has the language tools to build S-expressions elegantly. Many products built on the web platform boil down to developers writing code using three technologies:
React gives developers the opportunity to write all of these things inside of their JavaScript files. For example, one can write a pretty simple component.
function Component() {const [count, setCount] = React.useState(0);return (<button className="my-class" onClick={() => setCount(count + 1)}>{count}</button>);}
Having the ability to write something that looks like HTML in the JavaScript
makes it very easy to learn and work with. However, XML is a pretty poor data
type to use for this problem. The JSX actually gets transformed into function
calls. Particularly, this is accomplished with
React.createElement
Hmm, positional arguments! What is a data type that exists in JavaScript that helps us with positions? Arrays! Could that button above be made like...
function Component() {const [count, setCount] = React.useState(0);return ["button" {className: "my-class", onClick: () => setCount(count + 1)} ["count"]]}
Was this ever explored as an alternative to JSX? While dumbed down, it does use native data structures which would have an advantage of being more easily able to parse out than jsx. However, since JavaScript does not have as strong of an ability to create data with expressions, developers do not really gain anything.
Lets say you had a component that looked like this
function Component({ children }) {const { data, loading, error } = useGetMyData();if (loading) {return;<div><div>loading...</div>{children}</div>;}if (error) {console.error("Uh oh!");return (<div><div style={{ color: "red" }}>{error.message}</div>{children}</div>);}return (<div><div>Data:</div><div style={{ color: "green" }}>{data}</div>{children}</div>);}
One can write something like an Immediately-invoked Function Expression (IIFE) in an effort to make this more expressive:
function Component({ children }) {const { data, loading, error } = useGetMyData();return (<div>{(function () {if (loading) {return (<div>loading...</div>);}if (error) {console.error("Uh oh!");return (<div style={{ color: "red" }}>{error.message}</div>);}return (<><div>Data:</div><div style={{ color: "green" }}>{data}</div></>);})()}{children}</div>);}
JSX already has enough ugliness to it, adding an IIFE arguably makes things even more confusing and perhaps harder in the future to debug. One could also make another component, but that would be even more boilerplate and code to maintain. This isn't a problem with React. React wants you to put an expression in there. Technically, it is possible to do with nested ternary operators, but even that gets more confusing.
Clojure as stated before takes the attitude of using a vector (similar to an array in js) to express the markup as opposed to XML in a format called hiccup. This can become even denser in fewer lines of code with clojure:
(defn example [& children](let [{:keys [data loading error]} (useGetMyData)](into[:div(condloading [:div "loading"]error [:div {:style {:color "red"}} (error :message)]:else [:<>[:div "data:"][:div {:style {:color "green"}} data]])]children)))
Clojure can pull this off because unlike JavaScript, everything is an
expression! Additionally, like many of these examples, this is only the
beginning. What I really wanted to do in the past was define enums to represent
states of the application based on loading, data and error states. Then, I
wanted to write a switch statement inside of the JSX on the enum to
conditionally render. On the other hand, Clojure gives allows one to use a
cond
case
However, for those of us that still do love JavaScript (yes, I do!), there is a pattern matching proposal that TC-39 has on their radar. So perhaps one day my dream of a switch statement inside of JSX will come true.
One of the reasons why I decided to move onto a Clojure shop was that I really
enjoyed functional programming models and I wanted to learn more using one of
the marquee languages for doing it. Even if you don't intend on using Clojure or
ClojureScript in production, it is definitely a useful exercise to try building
functional declarative UIs in them. Try using ClojureScript and Reagent to see
what you do in a language that barely supports mutability of any kind and how
that motivates the JavaScript that you will write in the future. Previously
working with TypeScript has made me miss types and appreciate the runtime checks
of spec
If you want to work with Clojure and ClojureScript professionally, we are also hiring at Reify Health! While you do not need to know Clojure prior to joining (I didn't!), having a ❤ for functional programming is always a plus!