We're planting a tree for every job application! Click here to learn more

How Clojure helps you build powerful abstractions!

Cyprien Pannier

6 Jun 2018

4 min read

How Clojure helps you build powerful abstractions!
  • Clojure

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.

The Clojure core lib

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}

Multimethods and protocols

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:

  • when you have to add features to a dependency you don't have control on.
  • when you want to package a set of functions on a specific type.
(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"

Transducers

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, …

The CSP approach with core.async channels

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"

Concerned about typing?

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:

  • If the reason is performance you can help the Clojure compiler to avoid jvm runtime reflection using type hints (there is a flag available to warn you on reflection)
  • If the reason is detecting bugs at compile time, well Clojure does not have a easy way to do that. There is the core.typed initiative but i found it to produce too much overhead over a codebase. Some people were using it it is definitely an interesting project but its not the way I love to write Clojure code. Being dynamic gives you so much power and fastness (to code), I just embrace it!

Clojure is an opinionated language, love it or hate it

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

Did you like this article?

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

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub