Introducing Clojure

Clojure

2026-08-20 09:00

Where we are

What this unit assumes

You have written programs in a mainstream imperative language.

Variables, objects, methods, loops.

Bring it — but hold it loosely.

What you will be able to do

  1. Place Clojure in the Lisp family.
  2. State what makes a language functional.
  3. Explain structural sharing.
  4. Separate values from identities.
  5. Read prefix notation.
  6. Explain the two uses of parentheses.
  7. Call Java from Clojure.
  8. Say what hosting on the JVM buys and costs.
  9. Explain why JVM threads matter.

Three pillars at once

What Clojure is

A simple and succinct programming language designed to leverage easily both legacy code and modern multicore processors.

Where each half comes from

  • simplicity — sparse, regular syntax
  • succinctness — dynamic typing, functions-as-values
  • legacy code — hosted on the JVM
  • multicore — immutable data, concurrency constructs

Three pillars, inseparable

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.

What is actually hard

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.

Clojure as a modern Lisp

Lisp is not a language

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.

Not a museum piece

Lisp implementations run:

  • NASA’s Pathfinder mission-planning software
  • algorithmic trading at hedge funds
  • flight-delay prediction, data mining
  • natural language processing, expert systems
  • bio-informatics, robotics
  • electronic design automation
  • next-generation databases

What Lisp gave everyone else

  • conditionals
  • automatic garbage collection
  • macros
  • functions as language values

Several things you think of as ordinary started here.

Where Clojure sits

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.

What makes a language functional

The minimum requirement

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.

Three features that come with it

  • pure functions with referential transparency
  • immutable data structures as the default
  • controlled, explicit changes to state

They are interrelated, not independent.

Pure and referentially transparent

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.

Why immutable by default

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.

Clojure is pragmatic, not pure

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.

Persistent structures and structural sharing

The obvious objection

“Hold on — passing arguments by value and copying data structures everywhere is expensive, and I need to change the values of my variables!”

Theory vs reality

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 before

The tree xs — immutable nodes, immutable references.

The tree after

The new tree ys, sharing all it can with xs.

What actually happened

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\]

The sentence to remember

You get the safety of passing by value with the speed of passing by reference.

Values, identities, and controlled change

A value cannot change

\(42\) is \(42\).

Subtracting 2 does not change it. It gives \(40\).

This extends to all values, not just numbers.

An identity can

A variable acting as an identity is a container.

Different values may be put in it at different times.

What Clojure guarantees

  • 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.

Built into the core

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.

Prefix notation

Four steps over the hump

  • initially ignore the parentheses
  • consider how other languages use them
  • see them as units of value
  • embrace them

Ignore them

(get-url "http://example.com")

You guessed it makes an HTTP request. You were right.

Concatenation

(str "Hello, " "World!")
;; Result: "Hello, World!"

Other languages use an operator:

"Hello from " + "a language " + "with operators";

That is infix. Clojure is prefix, for everything.

More arguments, not more operators

(str "Hello from " "Clojure with " "lots of " " arguments")
;; Result: "Hello from Clojure with lots of arguments"

(+ 1 2)
;; Result: 3
(+ 1 2 3)
;; Result: 6

Two advantages

There are no operators.

str and + 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.

Now nest

3 + 4 * 2 needs precedence rules.

3 + (4 * 2) disambiguates.

Clojure makes that explicitness a requirement:

(+ 3 (* 4 2))

Solve it inside out

(+ 3 (* 4 2))
(+ 3 8)
;; Result: 11

The trade

Operators make arithmetic more concise.

Clojure makes calling functions completely consistent.

What parentheses are for

Two purposes

Calling functions

Constructing lists

Calling functions

Inside a set of parentheses the first form is always a function, macro, or special form. The rest are arguments.

Calling a function.

Nested calls.

The mnemonic

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.

Constructing lists

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.

Why that matters

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.

Do not match parentheses by hand

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.

The JVM underneath

Java is three things

  • a language
  • a virtual machine
  • a standard library

Clojure compiles to bytecode. It does not use the Java language, but it does need the library.

Where the seam shows

  • uses Java types directly — strings are String, numerals are Long, collections implement Java interfaces
  • sometimes wrapsclojure.string delegates to java.lang.String
  • often no wrapperabs, exp, log, sin, cos, tan need interop

Java’s object model, briefly

  • class hierarchy, single inheritance
  • interfaces as outlines of method signatures
  • one public class per file, on the classpath
  • fully qualified name = package + class: java.lang.Math

Everything in java.lang is imported by default — which is why String works unqualified.

Why hosted at all

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.

What that frees up

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).

What you get for it

Joda Time · JDBC drivers · Jetty · Bouncy Castle · Selenium WebDriver · Apache Commons

Plus JVM monitoring, VisualVM, YourKit, New Relic.

Calling Java from Clojure

The dot operator

Read (. A B ...) as

“in the scope of A do B with arguments…”

Three examples

(. Math PI)
;; Result: 3.141592653589793
(. Math abs -3)
;; Result: 3
(. "foo" toUpperCase)
;; Result: "FOO"

Static members use a slash

Math/PI
;; Result: 3.141592653589793
(Math/abs -3)
;; Result: 3

PI is a field — no parentheses needed.

abs is a method — parentheses required.

Instance methods use a leading dot

(.toUpperCase "foo")
;; Result: "FOO"

Constructors: new or a trailing dot

(new Integer "42")
;; Result: 42
(Integer. "42")
;; Result: 42

Why the sugar is safe

During 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.

Threads and the concurrency story

Why the JVM matters here

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.

The problem that creates

With one thread, evaluation is serial and easy to follow.

Add threads and you must ask:

  • can two threads change the same state at once?
  • can a change be atomic, so nobody sees a corrupt in-progress state?

Java’s position

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.

Clojure’s answer

Core data structures are immutable, so shared mutable state is largely moot.

Where it is needed:

  • vars, atoms, refs, agents — defined semantics for change
  • fast reads mid-change — a snapshot of old values is kept
  • futures and promises — parallelism without shared state

Enforced, not merely allowed

Hickey implemented constructs that not only allow correctness —

they enforce it at the language level.

Summary

The three pillars, transited

Functional programming with immutable data structures.

Lisp syntax.

Host interop.

You can now read basic Lisp and basic interop code.

The six things to carry away

  • Three pillars, mutually explaining, none complete alone.
  • Functions as values is the minimum bar; purity and immutability follow.
  • Structural sharing makes immutability affordable.
  • Values never change; identities hold different values over time.
  • No operators; parentheses call functions and build the lists your program is made of.
  • The JVM is not hidden — that is a deliberate trade.

Where next

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.