Lecture notes — Clojure Elements: Data Structures and Functions
ver. 1.0.0
← Clojure Elements: Data Structures and Functions
Where we are
In Introducing Clojure you learned to read Clojure: prefix notation, what parentheses are for, the value/identity distinction, and enough Java interop to call Math/abs.
Reading is not writing. This unit closes that gap, and it is the longest and most reference-like unit in the module because it has to hand you the whole working vocabulary at once.
Almost everything here is a form to type rather than read. The REPL is not an accessory to learning Clojure — it is where the learning happens, which is why we start there.
What you will be able to do
work-at-the-repl— Evaluate expressions at the REPL and use its magic variables and documentation tools.explain-prefix-notation-consequences— Explain why prefix notation makes code easy to generate and manipulate.use-clojure-scalars— Work with Clojure’s scalar types: nil, booleans, characters, strings and the number tower.distinguish-symbols-and-keywords— Distinguish a symbol from a keyword and say when to reach for each.choose-a-collection— Choose between a list, a vector and a map, and use each one’s core operations.use-the-sequence-abstraction— Use the ISeq abstraction that unifies every Clojure collection.structure-a-program— Define functions, bind locals with let, and sequence side effects with do.choose-a-conditional— Choose the right conditional form from if, if-not, cond, when and when-not.iterate-functionally— Replace imperative loops with recur, the sequence functions, and list comprehension.use-threading-macros— Flatten nested calls into a readable pipeline with the threading macros.
What we will cover
- REPL — interactive evaluation, magic variables, and documentation lookup.
- Prefix notation — the uniform syntax, and why it makes code easy to generate.
- Numbers and ratios — the numeric tower, arbitrary precision, and contagiousness.
- Symbols and keywords — identifiers that resolve, versus values that evaluate to themselves.
- Persistent collections — lists, vectors and maps, and what each is good at.
- ISeq —
first,restandconsacross every collection. - The let form — lexical local bindings.
- loop/recur — stack-safe recursion in place of iteration.
- Higher-order sequence functions —
map,filter,remove,reduce, andfor. - Threading macros — pipelining nested operations into a readable order.
From reading to writing
The four parts of this unit:
- Coding at the REPL — the interactive loop, plus the documentation tools that let you answer your own questions
- Data structures — the scalars, then lists, vectors, maps, and the abstraction that unifies them
- Program structure —
defn,let,do, and reader macros - Program flow — conditionals, logical functions, functional iteration, and threading macros
By the end you will have the vocabulary to write small to medium programs, which is what every later unit assumes.
Learning outcomes
- work-at-the-repl: Evaluate expressions at the REPL and use its magic variables and documentation tools.
- explain-prefix-notation-consequences: Explain why prefix notation makes code easy to generate and manipulate.
Coding at the REPL
Start with tradition:
user> (println "Hello, world!")
Hello, world!
=> nilTwo lines, and the difference between them is the REPL in miniature. println is unusual for Clojure because it is a side-effecting function: it prints a string to standard out and then returns nil.
Hello, world!was printed during the REPL’s evaluation phase=> nilwas printed during its print phase
Normally you want pure functions that only return a result and do not modify the world.
From here on the book omits the user> prompt and begins result lines with ;=>. It is a convention designed to make code easy to copy-paste between files and REPLs.
The magic variables
Four of them, and they save real typing:
*1,*2,*3— the last, second-last and third-last successfully read forms*e— the last error
Each time a form evaluates successfully its value goes into *1, the old *1 moves to *2, and the old *2 moves to *3.
"expression 1"
;=> "expression 1"
"expression 2"
;=> "expression 2"
*1
;=> "expression 2"
*3
;=> "expression 1"Multiple forms on one line each shift the chain:
"a" "b" "c"
;=> "a"
;=> "b"
;=> "c"
*3
;=> "a"(def a-str *1) captures the value that was in *1 at that moment. Evaluate something else and *1 moves on, but a-str still holds what it captured.
On an error the numbered variables stay where they were, and the error binds to *e:
( ) )
;=> ( )
RuntimeException Unmatched delimiter: )
*1
;=> ( )
*e
;=> #<ReaderException ... Unmatched delimiter: )>Answering your own questions
Three functions matter more than they look, because they turn the REPL from a place that evaluates into a place you can explore:
doc— prints a function’s docstring and argument specfind-doc— searches docstrings by pattern, for when you do not know the nameapropos— searches names

doc output for +, showing the argument spec alongside the docstring.Learning outcomes
- work-at-the-repl: Evaluate expressions at the REPL and use its magic variables and documentation tools.
Concepts
- repl: introduces interactive evaluation, the magic variables, and the documentation tools
Syntax revisited
The previous unit explained prefix notation and deferred the deeper reason. This is that reason.
You already write prefix notation
Prefix notation feels alien for arithmetic — (+ 1 2) rather than 1 + 2. But consider calling a function in Ruby:
add(1, 2)
Look closely and this is also prefix notation: the function name appears first, followed by arguments. Clojure moves the parenthesis and drops the comma, since whitespace is enough to delimit arguments:
(add 1 2)In most languages, mathematical functions are special cases built into the language as operators so that maths can be written infix. Clojure avoids the special case by not having operators at all — the maths functions are ordinary functions, and all functions work the same way.
What the regularity buys
By avoiding special cases and relying on the same notation everywhere, Clojure gets the advantages of having no syntax. The main one:
it makes it easy to generate and manipulate code.
The book’s example is cond:
(def x 1)
;=> #'user/x
(cond
(> x 0) "greater!"
(= x 0) "zero!"
(< x 0) "lesser!")
;=> "greater!"This is a nested list containing an even number of expressions in pairs. The first element of each pair is a test; the second is what gets evaluated and returned if the test succeeds.
Generating such a list is easy — compare it with generating a case statement in Java. This is your first concrete taste of why homoiconicity matters, and the two macro units later in this module cash it in completely.
Whitespace and comments
Clojure does not need commas to delimit elements. It treats them as whitespace and ignores them, so all of these are equivalent:
(+ 1 2 3 4 5)
;=> 15
(+ 1, 2, 3, 4, 5)
;=> 15
(+ 1,,,,2,3 4,,5)
;=> 15The printer does use commas when echoing a map, purely for readability:
(def a-map {:a 1 :b 2 :c 3})
user> a-map
{:a 1, :c 3, :b 2}The key order differs because hash maps are not ordered — it makes no difference to the map, only to how it prints.
Comments use semicolons. By convention: one after code on the same line, two for a whole commented line, three for a block. For multiple lines there is the comment macro, which ignores the forms passed in and returns nil:
(comment
(defn this-is-not-working [x y]
(+ x y)))
;=> nilLearning outcomes
- explain-prefix-notation-consequences: Explain why prefix notation makes code easy to generate and manipulate.
Concepts
- prefix-notation: explains the uniform prefix syntax and the code-generation advantage it produces
Scalars and the number tower
nil, truth and falsehood
nil is equivalent to null in Java and nil in Ruby — it means “nothing”. Calling a function on nil may lead to a NullPointerException, though core Clojure functions try to do something reasonable.
The truthiness rule is short enough to memorise:
Everything other than
falseandnilis considered true.
There is an explicit true when you need it.
Characters and strings
Both are Java’s. Characters are unsigned 16-bit UTF-16 code points, written with the backslash reader macro: \a, \g. Strings are Java strings in double quotes — single quotes are a reader macro meaning something else entirely.
Because they are Java strings, the Java String API is directly useful:
(.contains "clojure-in-action" "-")
(.endsWith "program.clj" ".clj")Both return true. Note the leading periods — that is the interop syntax from the previous unit.
Numbers
Most of the time you are using 64-bit integers (Java long) or 64-bit floats (Java double). When you need more range there are big integers and big decimals. And Clojure adds a less common type: the ratio, created when two integers divide without reducing further — (/ 4 9) gives 4/9.
| Type | Syntax examples | Contagiousness |
|---|---|---|
| Integer | 42, 0x2a, 052, 2r101010, -42 |
0 (lowest) |
| Big integer | 42N, 0x2aN, 052N |
1 |
| Ratio | 1/3, -2/4 |
2 |
| Big decimal | 2.78M, 278e-2M |
3 |
| Floating point | 2.78, 278e-2 |
4 (highest) |
Contagiousness decides mixed results: the most contagious type infects the result. Watch it climb:
(+ 1 1N)
;=> 2N
(+ 1 1N 1/2)
;=> 5/2
(+ 1 1N 1/2 0.5M)
;=> 3.0M
(+ 1 1N 1/2 0.5M 0.5)
;=> 3.5Adding, subtracting and multiplying integers can overflow — dividing cannot, since out-of-range division produces a ratio. Normally overflow throws:
user> (inc 9223372036854775807)
ArithmeticException integer overflowThe variants +', -', *', inc' and dec' — spelled identically but with a trailing single quote — autopromote to big integers instead:
user> (inc' 9223372036854775807)
;=> 9223372036854775808NLearning outcomes
- use-clojure-scalars: Work with Clojure’s scalar types: nil, booleans, characters, strings and the number tower.
Concepts
- ratios-and-numbers: details the numeric types, arbitrary precision, exact ratios, and overflow behaviour
Symbols and keywords
Symbols are the identifiers in a Clojure program — the names that signify values. In (+ 1 2), the + is a symbol signifying the addition function.
Because Clojure separates reading from evaluating, a symbol has two distinct aspects: its existence in the program data structure after reading, and the value it resolves to. Symbols by themselves are just names with an optional namespace; when an expression is evaluated they are replaced with the value they signify.
The syntax, which is easier to recognise than to state. A symbol is any run of alphanumerics or the characters *!_?$%&=<>, with restrictions: no leading number; no number as second character after -, + or . (so they cannot be confused with number literals); and at most one /, in the middle, separating namespace from name.
- valid:
foo,foo/bar,->Bar,-foo,foo?,foo-bar,foo+bar - invalid:
/bar,/foo,+1foo
Quoting makes a symbol data
arglebarg
CompilerException ... Unable to resolve symbol: arglebarg in this context.
'arglebarg
;=> arglebargThe first fails because arglebarg is not bound to anything. The second evaluates the symbol itself as a value. The quote tells the reader that the next form is literal data, not code to evaluate later.
Keywords are built for that job
In practice you will almost never quote a symbol to use it as data, because Clojure has a type specifically for the purpose.
A keyword is sort of like an autoquoted symbol: keywords never reference some other value and always evaluate to themselves.
Keyword syntax is almost symbol syntax with a leading colon: :foo, :foo/bar, :->foo, :+. You will use them constantly — typically as keys in hash maps and as enumerated values.
Converting between them
(keyword "foo")
;=> :foo
(symbol "foo" "bar")
;=> foo/bar
(name :foo/bar)
;=> "bar"
(namespace :foo)
;=> nil
(name "baz")
;=> "baz"Two details from those examples: namespace returns nil when there is no namespace part, and name returns strings unchanged.
Learning outcomes
- distinguish-symbols-and-keywords: Distinguish a symbol from a keyword and say when to reach for each.
Concepts
- symbols-and-keywords: explains identifier resolution for symbols versus self-evaluating keywords
Lists, vectors and maps
Three collections. They differ in what they are good at, not merely in syntax.
Lists
Singly linked, so it is easy to go from first to last and impossible to go backward. Items can only be added or removed at the front. That constraint has a payoff: multiple lists can share the same tails, which makes lists the simplest possible immutable data structure.
(list 1 2 3 4 5)
;=> (1 2 3 4 5)
(conj (list 1 2 3 4 5) 6)
;=> (6 1 2 3 4 5)
(conj (list 1 2 3) 4 5 6)
;=> (6 5 4 1 2 3)Note where conj puts things. Conjoining several at once gives the same result as conjoining one at a time.
Treat a list as a stack with peek and pop:
(peek (list 1 2 3))
;=> 1
(pop (list 1 2 3))
;=> (2 3)
(peek (list))
;=> nil
(pop (list))
IllegalStateException Can't pop empty listcount is constant time.
The compiler reads lists as code. So this fails:
(def three-numbers (1 2 3))It tries to call 1 as a function. Quote it to say “this is data”:
(def three-numbers '(1 2 3))
;=> #'user/three-numbersEvery Clojure beginner hits this once.
Vectors
Indexed, with [] literal syntax.
(vector 10 20 30 40 50)
;=> [10 20 30 40 50]
(def the-vector [10 20 30 40 50])
(get the-vector 2)
;=> 30
(nth the-vector 2)
;=> 30get and nth agree until you go out of range, and then they differ — which is the thing to remember:
(get the-vector 10)
;=> nil
(nth the-vector 10)
IndexOutOfBoundsExceptionassoc returns a new vector with one index changed:
(assoc the-vector 2 25)
;=> [10 20 25 40 50]Maps
Literal {}, or the hash-map function:
(def the-map {:a 1 :b 2 :c 3})
(hash-map :a 1 :b 2 :c 3)
;=> {:a 1, :c 3, :b 2}Both the map and the keyword are callable, so there are two lookup styles, plus a default:
(the-map :b)
;=> 2
(:b the-map)
;=> 2
(:z the-map 26)
;=> 26assoc adds, dissoc removes — both returning new maps:
(def updated-map (assoc the-map :d 4))
;=> {:d 4, :a 1, :b 2, :c 3}
(dissoc updated-map :a)
;=> {:b 2, :c 3, :d 4}Nested data
Real data nests, so three functions take a path rather than a key:
(assoc-in users [:kyle :summary :average :monthly] 3000)
;=> {:kyle {:date-joined "2009-01-01",
;=> :summary {:average {:monthly 3000, :yearly 12000}}}}
(get-in users [:kyle :summary :average :monthly])
;=> 1000
(update-in users [:kyle :summary :average :monthly] + 500)
;=> {:kyle {... :monthly 1500 ...}}Their general forms:
(assoc-in map [key & more-keys] value)
(update-in map [key & more-keys] update-function & args)
If a nested map does not exist along the way, assoc-in creates it. And update-in takes a function rather than a value, which is the difference between the two.
Learning outcomes
- choose-a-collection: Choose between a list, a vector and a map, and use each one’s core operations.
Concepts
- persistent-collections: covers immutable lists, indexed vectors and associative maps, and their operations
The sequence abstraction
This section is short and structurally the most important in the unit.
The ISeq interface provides three functions — first, rest and cons — and every collection supports them.
(first (list 1 2 3))
;=> 1
(rest (list 1 2 3))
;=> (2 3)
(first [1 2 3])
;=> 1
(rest [1 2 3])
;=> (2 3)Two results are worth pausing on:
(first {:a 1 :b 2})
;=> [:b 2]
(rest {:a 1 :b 2})
;=> ([:a 1])A map, viewed as a sequence, is a sequence of key/value pairs — so first gives you a pair, not a key.
(first [])
;=> nil
(rest [])
;=> ()rest of an empty collection is (), not nil. That is why you can keep calling rest without a nil check at every step.
And cons prepends:
(cons 1 [2 3 4 5])
;=> (1 2 3 4 5)Every collection presenting the same interface is what makes the rest of the unit work. When we reach map, filter, remove and reduce, none of them needs to know whether it was handed a list, a vector or a map. One abstraction, defined here, carries the whole module.
Learning outcomes
- use-the-sequence-abstraction: Use the ISeq abstraction that unifies every Clojure collection.
- choose-a-collection: Choose between a list, a vector and a map, and use each one’s core operations.
Concepts
- iseq: introduces the sequence interface — first, rest, cons — shared across all collections
Structuring a program
Functions
(defn addition-function [x y]
(+ x y))And it is worth seeing what defn actually is — def combined with fn:
(def addition-function
(fn [x y]
(+ x y)))Once that equivalence is visible, a lot follows. A function is a value bound to a name, exactly as unit 1 promised; fn produces one without naming it. Functions may also have variable arity, with different bodies for different argument counts.
The let form
let introduces lexical local bindings. The motivation is legibility, and the book’s example shows it. Written as one expression:
(defn average-pets []
(/ (apply + (map :number-pets (vals users))) (count users)))Written with let, each intermediate step gets a name:
(defn average-pets []
(let [user-data (vals users)
pet-counts (map :number-pets user-data)
total (apply + pet-counts)]
(/ total (count users))))Not shorter. Legible, which is the point.
Bindings are sequential, so a later one may refer to an earlier one:
(let [x 1
y (+ x 1)
z (+ y 1)]
z)
;=> 3Bind to _ when you need the binding to happen but do not need the value — typically a side-effecting call inside a let. It signals intent to the reader instead of inventing a name nobody uses.
Side effects with do
In a world without state and side effects, a function whose body is several expressions would be equivalent to one containing only the last:
(defn do-many-things []
(do-first-thing)
(do-another-thing)
(return-final-value))With side effects the earlier calls matter, so do exists: it evaluates several expressions and returns the last.
Where you actually need it is the useful part. if takes single expressions for its branches, so a branch that must do several things needs an explicit do:
(if (is-something-true?)
(do
(log-message "in true branch")
(store-something-in-db)
(return-useful-value)))Function bodies and when already have an implicit do, which is why you rarely write it there.
Reader macros
Notation the reader expands before evaluation ever happens — the quote ', the backslash for characters, the semicolon for comments. They are the mechanism behind several things you have already used, and they return when we write macros.
Learning outcomes
- structure-a-program: Define functions, bind locals with let, and sequence side effects with do.
Concepts
- let-form: explains lexical local bindings and the underscore convention
- symbols-and-keywords: uses symbols to create lexical names and var bindings for functions
Conditionals and logic
Five forms, two axes
(if test consequent alternative)
(if-not test consequent alternative)
(cond & clauses)
(when test & body)
(when-not test & body)
Rather than memorising five things, notice what separates them:
- Is the test negated?
ifversusif-not,whenversuswhen-not. - Is there an else branch?
ifhas one and takes single expressions.whenhas none but takes a body, implicitly wrapped indo.
So: reach for when when there is no alternative and the body has several expressions, and you avoid writing do yourself.
(if (> 5 2) "yes" "no")
;=> "yes"
(if-not (> 5 2) "yes" "no")
;=> "no"
(cond
(> x 0) "greater!"
(= x 0) "zero!"
(< x 0) "lesser!")
;=> "greater!"
(when (> 5 2)
(println "five")
(println "is")
(println "greater")
"done")
;=> "done"Logical functions return values
This is the part that surprises people. and and or do not return true and false — they return the value that decided the result.
(and)
;=> true
(and :a :b :c)
;=> :c
(and :a nil :c)
;=> nil
(and :a false :c)
;=> false
(and 0 "")
;=> ""and returns the last value if all are truthy, and the actual falsey value that stopped it otherwise — nil or false, whichever it met.
(or)
;=> nil
(or :a :b :c)
;=> :a
(or :a nil :c)
;=> :a
(or nil false)
;=> false
(or false nil)
;=> nilor returns the first truthy value, or the last falsey one.
(not true)
;=> false
(not 1)
;=> false
(not nil)
;=> trueComparisons chain, which is worth knowing:
(< 2 4 6 8)
;=> trueReturning values rather than booleans is what makes or usable for supplying defaults. Notice it now rather than being surprised later.

= with == for numeric equality.Learning outcomes
- choose-a-conditional: Choose the right conditional form from if, if-not, cond, when and when-not.
Concepts
- higher-order-sequence-functions: sets up the sequence-oriented functions the next section builds on
Functional iteration
Unit 1 said repetition happens by recursion rather than iteration. Here is the actual vocabulary — and there is more of it than you might expect.
while
(while test & body)
(while (request-on-queue?)
(handle-request (pop-request-queue)))The least Clojure-like of the group, useful for polling something that changes outside your control.
loop / recur
The primary stack-safe mechanism. loop establishes bindings like let; recur rebinds them and jumps back.

factorial implemented with loop/recur.The book’s fact-loop-invalid shows what happens otherwise:
(defn fact-loop-invalid [n]
(loop [current n fact 1]
(if (= current 1)
fact
(recur (dec current) (* fact current)))
(println "Done, current value:" current)))The recur is not the last thing the loop body does, so this is a compile-time error rather than a silent bug. That is the good outcome — the compiler catches it.
doseq and dotimes
For side effects rather than values:
(defn dispatch-reporting-jobs [all-users]
(doseq [user all-users]
(run-report user)))
(dotimes [x 5]
(println "X is" x))dotimes prints 0 through 4 and returns nil. Both return nil, which is the signal you called them for their effects.
The sequence functions
These are what you will actually reach for.
map applies a function across one or more sequences:
(map inc [0 1 2 3])
;=> (1 2 3 4)
(map + [0 1 2 3] [0 1 2 3])
;=> (0 2 4 6)
(map + [0 1 2 3] [0 1 2])
;=> (0 2 4)With several sequences, each supplies an additional argument — and the result is as long as the shortest.
filter and remove are complementary, and comparing them makes the point:
(defn non-zero-expenses [expenses]
(let [non-zero? (fn [e] (not (zero? e)))]
(filter non-zero? expenses)))
;=> (-2 -1 1 2 3)
(defn non-zero-expenses [expenses]
(remove zero? expenses))
;=> (-2 -1 1 2 3)Same result, and remove needs no helper function at all.
reduce collapses a sequence to a value:
(defn factorial [n]
(let [numbers (range 1 (+ n 1))]
(reduce * numbers)))for, the list comprehension
Not a loop, despite the name — it builds a sequence.
(def chessboard-labels
(for [alpha "abcdefgh"
num (range 1 9)]
(str alpha num)))
;=> ("a1" "a2" "a3" ... "h7" "h8")Multiple binding sequences nest. :when filters:
(defn primes-less-than [n]
(for [x (range 2 (inc n))
:when (prime? x)]
x))
(primes-less-than 50)
;=> (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)and both features combine:
(defn pairs-for-primes [n]
(let [z (range 2 (inc n))]
(for [x z y z :when (prime? (+ x y))]
(list x y))))Every one of these works on any collection, because of the sequence abstraction two sections back.
Learning outcomes
- iterate-functionally: Replace imperative loops with recur, the sequence functions, and list comprehension.
- use-the-sequence-abstraction: Use the ISeq abstraction that unifies every Clojure collection.
Concepts
- loop-recur: explains stack-safe recursion and the tail-position requirement
- higher-order-sequence-functions: details map, filter, remove, reduce and list comprehension
Threading macros
The last form of the unit, and the one that most changes how finished code looks.
The problem
Compound interest, written directly:
(defn final-amount [principle rate time-periods]
(* (Math/pow (+ 1 (/ rate 100)) time-periods) principle))
(final-amount 100 20 1)
;=> 120.0
(final-amount 100 20 2)
;=> 144.0Correct, and it reads inside-out: to follow it you find the innermost form and work outward, in the reverse of the order things actually happen.
Thread-first
-> takes the result of each form and inserts it as the first argument of the next:
(defn final-amount-> [principle rate time-periods]
(-> rate
(/ 100)
(+ 1)
(Math/pow time-periods)
(* principle)))
(final-amount-> 100 20 1)
;=> 120.0
(final-amount-> 100 20 2)
;=> 144.0Same results, read top to bottom in the order the steps occur.

as-> threading a map through a series of operations, for cases where the value does not belong in the first position.The threading macros add no capability — they change legibility, and that is enough to make them idiomatic.
Notice also what they are: macros that rewrite one expression shape into another before evaluation. You have now used several — defn, when, cond, -> — without writing any. The macro units later in this module are about writing your own.
Learning outcomes
- use-threading-macros: Flatten nested calls into a readable pipeline with the threading macros.
Concepts
- threading-macros: introduces macros that pipeline nested operations into a readable linear order
What you can now write
That is the working vocabulary. It is a lot at once, and it is meant to be a reference you return to rather than something memorised in a sitting.
The through-line worth carrying forward: the sequence abstraction. Three functions defined in one short section are why map, filter, remove, reduce and for all work on anything you hand them.
Learning outcomes
- work-at-the-repl: Evaluate expressions at the REPL and use its magic variables and documentation tools.
- explain-prefix-notation-consequences: Explain why prefix notation makes code easy to generate and manipulate.
- use-clojure-scalars: Work with Clojure’s scalar types: nil, booleans, characters, strings and the number tower.
- distinguish-symbols-and-keywords: Distinguish a symbol from a keyword and say when to reach for each.
- choose-a-collection: Choose between a list, a vector and a map, and use each one’s core operations.
- use-the-sequence-abstraction: Use the ISeq abstraction that unifies every Clojure collection.
- structure-a-program: Define functions, bind locals with let, and sequence side effects with do.
- choose-a-conditional: Choose the right conditional form from if, if-not, cond, when and when-not.
- iterate-functionally: Replace imperative loops with recur, the sequence functions, and list comprehension.
- use-threading-macros: Flatten nested calls into a readable pipeline with the threading macros.
Concepts
- repl: summarises the foundational role of REPL-based interactive programming
- persistent-collections: recaps the core data types and abstractions before moving deeper
Conclusion
The REPL is where Clojure development happens, not an accessory to it.
Its evaluate-then-print phases explain why
printlnproduces two lines. The magic variables anddoc/find-doc/aproposare what let you answer your own questions without leaving it.Prefix notation is regular, and regularity makes code easy to generate.
add(1, 2)was already prefix. The payoff is thatcondis a nested list of pairs — trivial to generate compared with a Javacase. The macro units later depend entirely on this.Clojure’s scalars are richer than they look.
Everything but
falseandnilis true. Strings and characters are Java’s. Numbers form a tower with ratios and arbitrary precision, and contagiousness decides the type of a mixed result.Symbols resolve; keywords evaluate to themselves.
A symbol is an identifier that becomes the value it signifies. A keyword is the type built for being data, which is why map keys are keywords and not quoted symbols.
One sequence abstraction unifies every collection.
first,restandconswork on lists, vectors and maps alike. That is why the higher-order functions never need to know what they were handed.Loops are replaced by a vocabulary, not a single construct.
recurfor stack-safe recursion,doseq/dotimesfor effects,map/filter/remove/reducefor transformation,forfor comprehension. Then->to make the result readable.
Where next
The next unit, Building Blocks of Clojure, goes underneath what you have just learned: functions in depth including higher-order ones, let revisited with lexical closures, vars and binding, namespaces for organising code, destructuring for pulling structures apart inside a binding form, and the metadata and type-hint machinery that connects back to the JVM.