Clojure
2026-08-20 09:00
You have written programs in a mainstream imperative language.
Variables, objects, methods, loops.
Bring it — but hold it loosely.
A simple and succinct programming language designed to leverage easily both legacy code and modern multicore processors.
Lisp · Functional programming · The JVM
At every step they play on each other.
We take them one at a time anyway. We have to start somewhere.
Not the syntax. Not the JVM.
The truly mind-bending part comes from the shift from an imperative mindset to a functional programming approach to program design.
It is a style, designed in 1958 by John McCarthy.
Second only to Fortran among families still in active use.
Today: Common Lisp, Scheme, Emacs Lisp — and Clojure.
Lisp implementations run:
Several things you think of as ordinary started here.
In the family, but adhering to no single implementation.
Combines strengths of several Lisps, plus features from ML and Haskell.
Adds: pragmatic FP, symbiosis with existing runtimes, built-in concurrency.
Treat functions as something more than named subroutines.
A function is a value, like "hello" and 42 are values.
Pass them as arguments. Return them as results. First-class functions.
They are interrelated, not independent.
Pure — no side effects. No global state changes, no I/O.
Referentially transparent — same inputs, same output. Always.
Easier to reason about code that behaves consistently, without respect to the implicit environment it runs in.
It guarantees functions cannot alter the arguments passed to them.
That is what makes pure functions practical rather than merely possible.
In a simplistic sense: arguments are always passed by value.
Defaults encourage pure FP — immutability, higher-order functions, recursion over loops.
But some tasks are clearer with mutable state, so Clojure provides well-defined constructs for it.
And it does not require you to annotate side-effecting code.
“Hold on — passing arguments by value and copying data structures everywhere is expensive, and I need to change the values of my variables!”
In theory — changing an immutable structure gives a brand-new structure. You cannot change what is immutable.
In reality — Clojure uses structural sharing so only the minimum amount of copying happens.
The tree xs — immutable nodes, immutable references.
The new tree ys, sharing all it can with xs.
Adding value e creates new nodes only on the path to the root:
\[d',\ g',\ f'\]
and reuses the old nodes:
\[b,\ a,\ c,\ h\]
You get the safety of passing by value with the speed of passing by reference.
\(42\) is \(42\).
Subtracting 2 does not change it. It gives \(40\).
This extends to all values, not just numbers.
A variable acting as an identity is a container.
Different values may be put in it at different times.
One variable, atomically
Threads always see a consistent picture. Readers get the pre-change value; writers are held off.
Several variables, transactionally
STM changes them as a unit and rolls back if they do not all complete.
On another thread
Without blocking the main one.
Concurrency [is] so easy you have to work to make your programs not support it.
We only name these here. A later unit spends its whole length on them.
You guessed it makes an HTTP request. You were right.
That is infix. Clojure is prefix, for everything.
There are no operators.
strand+are both ordinary functions. One just has a nonalphabetic name. No precedence to memorise.
Variable arity is natural.
Add another argument without fear of forgetting an operator between them.
3 + 4 * 2 needs precedence rules.
3 + (4 * 2) disambiguates.
Operators make arithmetic more concise.
Clojure makes calling functions completely consistent.
Calling functions
Constructing lists
Inside a set of parentheses the first form is always a function, macro, or special form. The rest are arguments.


That left parenthesis is like a phone being held up to the function’s ear, getting ready to call it with the rest of the items up to the matching right parenthesis.
The most common use, and the least noticeable.
Your entire Clojure program is a series of lists.
The compiler reads your source as lists of function names and arguments.
The same language features are available at the compiler level and in normal program code.
That is what makes Lisp metaprogramming possible.
Two later units in this module are built on this one fact.
Expressions are building blocks — each a self-contained world of functionality that results in a value.
That consistency lets editors do structural editing.
Learn those tools. The parentheses become an advantage.
Clojure compiles to bytecode. It does not use the Java language, but it does need the library.
String, numerals are Long, collections implement Java interfacesclojure.string delegates to java.lang.Stringabs, exp, log, sin, cos, tan need interopjava.lang.MathEverything in java.lang is imported by default — which is why String works unqualified.
The best kind of engineering laziness.
Mature, ubiquitous VM. Open-source HotSpot with an advanced JIT and choice of garbage collectors. A myriad of third-party libraries.
The Clojure community is free to focus its time on a solid language design and higher-level abstractions instead of reinventing the VM wheel (and the bugs that come with it).
Joda Time · JDBC drivers · Jetty · Bouncy Castle · Selenium WebDriver · Apache Commons
Plus JVM monitoring, VisualVM, YourKit, New Relic.
Read (. A B ...) as
“in the scope of A do B with arguments…”
PI is a field — no parentheses needed.
abs is a method — parentheses required.
new or a trailing dotDuring macro expansion, the trailing dot expands to new and the others expand to the plain dot form.
They are literally equivalent by the time your code is evaluated.
Ruby and Python default runtimes: lightweight green threads, managed by the runtime.
JVM threads map directly to native system threads.
Multiple CPU cores for free. Genuine, performant parallelism.
With one thread, evaluation is serial and easy to follow.
Add threads and you must ask:
Java has all the tools to write safe concurrent programs with shared mutable state.
in practice it’s extremely difficult to write such programs correctly.
Core data structures are immutable, so shared mutable state is largely moot.
Where it is needed:
Hickey implemented constructs that not only allow correctness —
they enforce it at the language level.
Functional programming with immutable data structures.
Lisp syntax.
Host interop.
You can now read basic Lisp and basic interop code.
Clojure Elements: Data Structures and Functions gets you to the REPL.
Core data structures, program structure, program flow.
The point at which you write small programs instead of only reading them.
Clojure · Introducing Clojure