Cyprien Pannier
6 Jun 2018
•
4 min read
Clojure has its own way of doing things, often coming right out of the brain of the smart Clojure creator Rich Hickey. I'm strongly convinced by nearly all of them, and this is the main reason why Clojure is my preferred language, where Javascript could have been the top one with its recent ES6–7 syntax improvements.
Clojure gives you a solid toolbox to write rock solid concurrent code. It is a very opinionated language, and despite of providing good jvm compatibility, it does not always follow the hype of big programming tendances like reactive streams or actor based programming. The truth is Clojure (as the team behind it) has its own innovation path and introduces concepts it sees having a real use case for writing production code.
But wait, its not a niche, core async channels for example are directly inspired by Golang. Clojure is not a strange Lispy functional alternative, it is fun to write, easy to test and leads you productively to clean codebases leveraging strong tools like core.async channels, atoms, transducers, protocols, spec and others... When you get used to those primitives, you realize you get things done efficiently. Beware that you should force you to write comprehensible code, and comment your names, because things become quickly hard to read. But this is a broader programming problem.
Let's describe briefly some of the powerful tools you have at your disposal when going the Clojure way.
Functional all the way down, the Clojure core library has a very consistent and convenient API. It motivates you to do the same with your own abstractions. It's idiomatic to use the raw data structures like keywords, maps and sequences, which are first class citizens.
(def my-map {:a-key "coucou" :my-ns/a-key "namespaced coucou"})
(def my-vector [:un :deux])
(def my-list '("head" "second"))
(def my-set #{1 2})
When you need to mutate your vars, Clojure forces you to use clever wrappers like atoms (never found a use case yet for other ones like agents and STM). Your code is ensured to be thread safe this way, you don't have to handle locks by yourself, concurrency is a primary concern of the language.
(def counter (atom 0))
; the two following operations run in parallel
(future (swap! counter inc))
(future (swap! counter + 2))
(println @counter)
; 3
And for those who are afraid of parens, or about the levels of nesting when using a lispy language, Clojure provides two very convenient macros: the threading right macro: to be used for example when manipulating sequences because of the position where the initial argument is injected
(->>
[1 2 3]
(map inc)
(filter even?)
(reduce +))
; 6
the threading left macro: to be used for example when manipulating maps
(->
{:one 0}
(update :one inc)
(assoc :two 2))
; {:one 1 :two 2}
This is in the best way to approach polymorphism I've ever seen. So much cleaner and powerful than inheritance hierarchies.
Protocols are useful for two main cases in my opinion:
(defprotocol Prettifier
(prettify [this]))
(extend-type java.util.Date
Prettifier
(prettify [date] (format-date date)))
(prettify (Date.))
; "Sat Apr 21 2018"
Regarding multimethods, they are the ultimate flexibility, and are very simple to understand. It's a way of dispatching args on a corresponding function. When dispatching against types, it behaves the same way object inheritance works.
(defmulti debug (fn [arg] (class arg)))
(defmethod debug java.lang.String [arg]
(str "String: " arg))
(defmethod debug java.lang.Long [arg]
(str "Long: " arg))
(debug "toto")
; "String: toto"
(debug 1)
; "Long: 1"
This is something that is quite hard to implement with a typed language, they are a very nice way of functionally and lazily composing behaviors. A transducer is pretty hard to understand at first. It is close to a reducer, which takes an iterable object, an accumulator, and returns the final reduced value, but it actually returns another function, making it highly composable. It has the following simplified signature:
(whatever, input -> whatever) -> (whatever, input -> whatever)
It is so much powerful that the majority of Clojure core functions has been rewritten to support transducers too. Now you can write your behaviors as a composition of reducers, and apply them freely to any possible sequence implementation, core.async channel, …
When the current buzzword is reactive programming for a while, I often find it cleaner to use core.async channels, it's a lightweight and nice way of separating concerns using CSP (communicating sequential processes). This Clojure library is heavily inspired from the Go channels implementation. Well Reactive programming has its applications for sure, but from a backend point of view, I've never found a real use case that couldn't be answered cleanly and efficiently by raw Clojure + queues + core.async.
(require '[clojure.core.async :as a])
(def input-chan (a/chan))
(a/go (a/>! input-chan "my message"))
(println (a/<!! input-chan))
; "my message"
I love dynamic typing. Yes shame on me. But I love it it's a matter of taste and programming style. Still I understand how static typing can make you program evolve more safely and scale in a team point of view. However it can introduce friction with the productivity and reusability capabilities I like so much when programming in Clojure.
Some people also complain about Clojure being maps continuously passed around. Well maps are clearly a very used data structure in Clojure programs but once the prototyping step has passed you better use named function arguments and records to have a better code. Plus you can document and provide (runtime) validation using prismatic schema, or clojure.spec that has released more recently and propose a broader answer to the typing problem.
If you think serious code should be typed period, ok that's fine it's also a matter of personal preferences :)! I can at least give you my opinion:
I love it, I know other people that love it (in France! Montpellier! Coucou BeOpinion) I also know people that don't. It certainly deserves to be more known because it can definitely help projects and people as a well-engineered building tool.
And If you want to have a look at the tools and librairies used by the Clojure community, here a ThoughtWorks technology-style radar by JUXT: https://juxt.pro/radar.html
Cyprien Pannier
The noblest pleasure is the joy of understanding (da Vinci). Freelance senior software engineer (Clojure, JS, devops) #futureofwork #d14n
See other articles by Cyprien
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!