What I like about Clojure(script)
Last week I had a pleasure to get an introduction to Clojure and Clojure script by Karsten Shmidt aka Toxi.
We've learned how to use a set of libraries called http://thi.ng to work with web ui, linked data and SVG. You can find the code at http://github.com/thi-ng/ws-ldn-2.
I've never used Clojure or ClojureScript before beside a quick tutorials Learn X in Y Minutes and Introduction to Clojure – Modern dialect of Lisp
done before the workshop so I decided to write down my experience in this post.
Syntax
It's not that bad. I have never got parentheses paralysis (((((((lisp)))))))
as any Clojure supporting editor will match the ( and ) for you. The prefix notation (+ 1 2)
turned out to be easier to write than read but I guess this requires practice. What was more confusing was the use of #
for many different things like inline functions #(inc %)
, hash-sets (unoerdered sets) #{1 2 3}
and regular expressions #"pattern"
.
Functional programming
I've been interested in the topic for quite a while so I was familiar with the concepts of pure functions, immutability and functional compositions. Pure, functional JavaScript by Christian Johansen and JavaScript Allongé are good places to start.
Immutability
Having to "unlearn" Object-Oriented programming is perceived as the most difficult step in learning any Lisp.
After using Ramda, React.js for over a year and recently redux I have been brainwashed enough to understand why immutable state and pure functions rock.
You would be surprised how rarely we really have to use mutable data and how much easier it is to reason about your program if the side effects are limited.
First of all you can always redefine a let binding. It's not real mutation but good enough in the local context.
(let [poly (first (borough ?apoly))
poly (map f/parse-float (clojure.string/split poly #" "))]
)
For really mutable things like the application state we can always use Atoms. For the basic use you can think of them just like any JavaScript object.
(defonce app-state (r/atom {}))
(defn set-response-data!
"Storing ajax response in the application state <- this is a doc string"
[response]
(swap! app-state assoc :response response))
Having built complex data visualizations at Variable I try to use related approaches of Data (1)Oriented (2)Design (3) and Component (4)Entity (5)Systems (6) and focus more on data transformations and pipelines.
This has big impact on the libraries I use as well, suddenly jQuery style APIs like D3.js become less and less useful when you want to decouple your data processing from the rendering and draw visualizations more like a game engine would do.
Keywords
Forget about var EVENT_NAME = "eventString"
or your.lib.namespace.CONST_VALUE
type of thing. Keywords in clojure are symbolic identifiers that are super fast equality tests and are used for const values, object properties and flags.
JavaScript object props style
(io/request
{:uri "http://localhost:8000/query"
:method :post
;;:params {:limit 1000 :offset 1000}
:data {:spec (:query @app-state)})
The fact that they supported out of the box allows again to build libraries that are data driven instead of using custom class hierarchies, helper functions or weird JSX syntax like in ReactJS. Here is a snipped of hiccup syntax used in reagent:
[:div {id: "container"}
[:h1 "Hell.o!"]
[:div {:style {
:width "5px"
:height "5px"
:background "blue" }}]]
Core library
Should I used lodash, underscore or ramda? This problem doesn't exist in Clojure as the core library is rich and well though trough. This common base make good practices more popular and produces code that is familiar and easier to understand across the libraries.
Interfaces aka Protocols
Protocols in Clojure allow you to implement additional functionality for a type in a given namespace. Here is an example of how 2D vector is implemented in thi.ng:
;create a new vector
(def n (v/vec3 0 1 2)) -> v/vec3 0 1 2)
;get a key like in a hashmap
(:xy n) -> (v/vec2 0 1)
;get an element like in a vector
(n 0) -> 0
;use functions operating on lists
(first n) -> 0
REPL
Read–eval–print loop allows you to connect to your live running code and modify it on the fly.
I haven't use it that much yet but being able to inspect your app state, or update functions and re-run them without reloading the whole app changes the way you think about your programs.
JavaScript interop
The workshop was based on the goal of using linked data and visualizing them in the browser. We used Figwheel for hot reloading of code during development and thi.ng/Fabric as a graph store for our data.
Fabric queries are inspired by SPARQL but the whole Fabric library is not limited to the linked data but is based on using graphs for computations.
The screenshot above is from a homework where I use ClojureScript to load data from a Fabric server and display them on Google Maps. The data comes from Digital Docial project and it's Linked Data backend.
An example Fabric query I have used.
(def query '{
:prefixes {"org" "http://www.w3.org/ns/org#" "ds" "http://data.digitalsocial.eu/def/ontology/"}
:q [{:where [[?org "rdf:type" "org:Organization"]
[?org "rdfs:label" ?name]
[?org "org:hasPrimarySite" ?site]
[?site "pos:lat" ?lat]
[?site "pos:long" ?lon]
[?am "ds:organization" ?org]
]}]
:aggregate {?count (agg-count ?am)}
:group-by ?org
:select [?org ?count ?lat ?lon ?name]
})
Calling JS code from CLJS is supported and quite easy once you get used to the syntax. For example to call console.log("hello")
you would do:
(.log js/console "hello")
More about thi.ng
The thi.ng set of libraries is vast and a real pleasure to study. From Geometry transformations to linked data that we used in the workshop, to WebGL and color utils.
Even if you will never use Clojure in your work learning how for example represent color gradients as composed cosine functions can flip your world upside down. Just have a look at thi.ng.color.gradients.
And finally a small example that took me maybe 1h to write based on the code from the first London workshop on using WebGL from CLJS:
Will I switch?
Having used JavaScript for 95% of my projects in the last 3 years is a big investment so jumping a ship and switching to CLJS won't happen soon. Still it's a great experience and a thinking exercise.
I use many of the concepts from Clojure and functional languages in my everyday work and it's definitely influencing how I think about code. For example data driven approach and using plain objects and
lists will manifest itself greatly in the next version of PEX my WebGL library I develop with Henryk Wollik.
So I would encourage you to try and see how Clojure can change your workflow because there is always a better way, no matter what you do.