Clojure Guides_ Language_ clojure.core
Clojure Guides_ Language_ clojure.core
Clojure Guides_ Language_ clojure.core
core
This guide covers:
This guide is by no means comprehensive and does not try to explain each function/macro/form in
depth. It is an overview, the goal is to briefly explain the purpose of each item and provide links to other
articles with more information.
This work is licensed under a Creative Commons Attribution 3.0 Unported License
(https://creativecommons.org/licenses/by/3.0/) (including images & stylesheets). The source is available
on Github (https://github.com/clojure-doc/clojure-doc.github.io).
Binding
let
(let [bindings*] exprs*)
let takes a vector of symbol/expression pairs (bindings) followed by a variable number of expressions.
let allows binding of locals (roughly equivalent to variables in many other languages), and evaluates the
expressions (with those bindings in scope).
The body of a let statement also provides an implicit do that allows for multiple statements in the body
of let .
A basic example:
(let [x 1 y 2]
(println x y))
1 2
nil
let does its bindings sequentially, meaning it's possible to reference previous bindings:
(let [a 5
b (* a 2)
c (+ a b)]
(println "c is" c))
nil
let can also be nested, and the scope is lexically determined. This means that a binding's value is
determined by the nearest binding form for that symbol.
(let [x 1]
(println x) ; prints 1
(let [x 2]
(println x)) ; prints 2
(println x)) ; outside the nested let, prints 1 again
1
2
1
nil
def
(def symbol doc-string? init?)
def takes a symbol, an optional docstring, and an optional init value (although it is very rare that you
would omit the init value), and creates (or re-assigns) a var in the current namespace with the name of the
symbol. If an init value is supplied, the root binding of the var is assigned to that value. Redefining a var
with an init value will re-assign the root binding. If no init value is supplied and the var is newly-created, it
will be 'unbound', otherwise the var already exists and its value is unchanged.
(def x)
x ; x is an 'unbound' var, or nil in clojurescript
; => #object [clojure.lang.Var$Unbound 0x1de19fc6 "Unbound: #'user/x"]
(def x 7)
x ; x is now 7
; => 7
(def x)
x ; x is still 7, deffing with no value doesn't unbind an existing value
; => 7
def vs let
Superficially, def and let both give names to values. A key difference is that let bindings are scoped
and don't have a namespace, while vars created with def are 'global' and qualified to a namespace. A
name/binding that should only exist within (for example) a function should be created with let , and it's
very rarely needed/advisable to use def within a function or other form.
[7 5 "hi"]
See Vars and the Global Environment (https://clojure.org/reference/vars) in the official Clojure reference
documentation for more details, which also covers how to attach metadata to such Vars.
declare
(declare names*)
declare takes zero or more symbols and behaves as if you had used def on each without an init value.
declare provides a simple way of creating 'forward declarations'. This allows for referencing of a var
before it has been supplied a value.
There are much better methods of value-based dispatch or code architecture in general, but this presents
a simple situation forward declarations would be necessary.
(declare func<10 func<20)
;; without declare you will receive an error similar to:
;; "Unable to resolve symbol: func10 in this context"
(defn func<10 [x]
(cond
(< x 10) (func<10 (inc x))
(< x 20) (func<20 x)
:else "too far!"))
(defn func<20 [x]
(cond
(< x 10) (func<10 x)
(< x 20) "More than 10, less than 20"
:else "too far!"))
#'cljs.user/func<20
No matter which order you put func<10 and func<20 in, there will be a reference to a var that does not yet
exist when the compiler does the initial evaluation of top-level forms.
declare defines the var with no binding so that the the var exists when it is referenced later in the code.
defn
(defn name doc-string? attr-map? [params*] prepost-map? body)
(defn name doc-string? attr-map? ([params*] prepost-map? body)+ attr-map?)
defn takes a symbol, an optional doc string, an optional meta-data map, a vector of arguments and a
variable number of expressions.
defn is the primary way of defining functions. It allows for convenient definition of metadata about its
argslist and documentation (docstrings). defn inherently allows for quick documentation of functions that
can be retrieved with doc . This feature should be used almost universally.
Because defn has multiple forms and a handful of optional parameters, there's a separate section that
does a deep dive on defining functions (/articles/language/functions).
See the def Special Form (https://clojure.org/reference/special_forms#def) for more detail about how
defn provides additional metadata and convenience over def .
Branching/Grouping
do
(do exprs*)
do is used to 'group' expressions together. It executes the expressions in order and returns the return
value of the final expression. It is often used when it's necessary to treat multiple expressions as one
'block', like an argument to if .
if
(if test then else?)
if takes two or three expressions -- a condition expression, a 'then' expression, and optionally an 'else'
expression. If the 'else' expression is not supplied, it defaults to nil .
if is the primary method of conditional execution, and other conditionals are built upon if .
The condition is evaluated, and if its value is truthy -- anything except nil or false -- the 'then'
expression is evaluated and the result returned (the 'else' expression is not evaluated).
If the condition returns nil or false the 'else' expression is evaluated and returned (the 'then'
expression is not evaluated).
"second"
"third"
"third"
nil
(if (nil? (= 1 2)) "second" "third") ; differentiate between nil and false if nee
"third"
when
(when test body*)
when takes a test and zero or more body expressions. If the test returns a truthy value, all the body
expressions are evaluated in an implicit do , and the last return value is the return value of when . If the
test is not truthy, nil is returned (the body expressions are not evaluated at all).
Put another way, (when test body) is transformed to (if test (do body)) .
nil
;; (< 10 11) is true so both expressions are evaluated and the last value returne
(when (< 10 11) (println "hey") 10)
hey
10
There might be some unfamiliar syntax, but this demo shows that the when is transformed to an if
expression (this is an example of a macro (/articles/language/macros)).
(macroexpand '(when (< 3 4) (println "true text") (println "more true text")))
(if (< 3 4) (do (println "true text") (println "more true text")))
Looping
recur
(recur exprs*)
recur allows for self-recursion without consuming stack space proportional to the number of recursive
calls made. Due to the lack of tail-call optimization on the JVM currently, this is the only method of
recursion that does not consume excess stack space.
recur takes a number of arguments identical to the point of recursion. recur will evaluate those
arguments, rebind them at the point of recursion and resume execution at that point.
The point of recursion is the nearest function ( defn , fn ) or loop form determined lexically.
recur must be in the tail position of the recursion point expression. The tail position is the point in the
expression where a return value would otherwise be determined.
recur does not bind & in variadic functions and in these situations an empty seq must be passed by
recur .
(defn count-up
[result x y]
(if (= x y)
result
(recur (conj result x) (inc x) y)))
(count-up [] 0 10)
[0 1 2 3 4 5 6 7 8 9]
(defn factorial
([n]
(factorial n 1))
([n acc]
(if (zero? n)
acc
(recur (dec n) (* n acc)))))
(factorial 10)
3628800
loop
(loop [bindings*] exprs*)
loop takes a vector of symbol value pairs followed by a variable number of expressions.
loop establishes a recursion point for a recur expression inside its body. loop provides an implicit
let for bindings.
The implicit let that loop provides binds each symbol to the init-expression. recur then binds new
values when returning the execution point to loop .
(defn count-up
[start total]
(loop [result []
x start
y total]
(if (= x y)
result
(recur (conj result x) (inc x) y))))
(count-up 0 10)
[0 1 2 3 4 5 6 7 8 9]
(defn factorial
[n]
(loop [n n
acc 1]
(if (zero? n)
acc
(recur (dec n) (* acc n)))))
(factorial 10)
3628800
trampoline
([f])
([f & args])
trampoline takes a function and a variable number of arguments to pass to that function.
trampoline allows for mutual recursion without consuming stack space proportional to the number of
recursive calls made.
If the return value of that function is a function, trampoline calls that function with no arguments. If the
return value is not a function, trampoline simply returns that value.
Since trampoline calls the returned functions with no arguments, you must supply an anonymous
function that takes no arguments and calls the function you wish to recur to. This is usually done with
anonymous function literals #()
(declare count-up1 count-up2) ;; see `declare` for why this is needed
(defn count-up1
[result start total]
(if (= start total)
result
#(count-up2 (conj result start) (inc start) total))) ;; returns an anonymous
(defn count-up2 [result start total]
(if (= start total)
result
#(count-up1 (conj result start) (inc start) total))) ;; returns an anonymous
;; delete #_ to run the example:
#_(trampoline count-up1 [] 0 10)
#'cljs.user/count-up2
for
([seq-exprs body-expr])
for allows for list comprehensions. for assigns each sequential value in the collection to the binding
form and evaluates them rightmost first. The results are returned in a lazy sequence.
for allows for explicit let, when and while through use of :let [] , :when (expression) , and :while
(expression) in the binding vector.
(for [x [1 2 3] y [4 5 6]]
[x y])
([1 4] [1 5] [1 6] [2 4] [2 5] [2 6] [3 4] [3 5] [3 6])
:when only evaluates the body when a truthy value is returned by the expression provided
(for [x [1 2 3] y [4 5 6]
:when (and
(even? x)
(odd? y))]
[x y])
([2 5])
:while evaluates the body until a falsey value is reached. Note that elements of the second collection
are bound to y before a falsey value of (< x 2) is reached in the following example. This demonstrates
the order of the comprehension.
(for [x [1 2 3] y [4 5 6]
:while (< x 2)]
[x y])
([1 4] [1 5] [1 6])
doseq
([seq-exprs & body])
doseq takes a vector of pairs of [binding collection] and then one or more expressions as the
body .
doseq is similar to for except it does not return a sequence of results. doseq is generally intended for
execution of side-effects in the body, and thus returns nil .
doseq supports the same bindings as for - :let , :when , :while . For examples of those, see for
above.
(doseq [x [1 2 3] y [4 5 6]]
(println [x y]))
[1 4]
[1 5]
[1 6]
[2 4]
[2 5]
[2 6]
[3 4]
[3 5]
[3 6]
nil
run!
([proc coll])
If you have a single collection to loop over, just for side-effects, and a single function to call on each
element, you might prefer to use run! instead of doseq . The following are equivalent:
(doseq [x [1 2 3]]
(println x))
(run! println [1 2 3])
iterate
([f x])
A lazy sequence is returned consisting of the argument then each subsequent entry is the function
evaluated with the previous entry in the lazy sequence. In other words, the function is applied repeatedly
to produce each new entry in the sequence.
Since it is an infinite sequence, you need to use take or similar to get a finite number of elements.
(0 1 2 3 4 5 6 7 8 9)
reduce
([f coll])
([f val coll])
If an initial value is provided, reduce applies the function to that initial value and the first item in the
collection. The function is then applied to that result and the second item in the collection, and so on. If the
collection is empty, the initial value is returned (and the function is not called).
If an initial value is not provided, and the behavior is a bit more complicated:
If the collection is empty, the function is called with no arguments and the result is returned,
If the collection has one element, that element is returned, and the function is not called,
Otherwise, the function is called with the first two elements of the collection, and then with the result
of that call and the third element, and so on.
15
15
(reduce + [1]) ; 1 -- + is not called
reductions
([f coll])
([f val coll])
reductions returns a lazy sequence of the intermediate values that would be produced during calls to
reduce with the same arguments.
Like reduce , if no initial value is provided, and the collection is empty, the function is called with no
arguments and the result is the only value in the lazy sequence; if no initial value is provided, and the
collection has only one element, that element is the only value in the lazy sequence, and the function is
not called.
map
([f coll])
([f c1 c2])
([f c1 c2 c3])
([f c1 c2 c3 & colls])
map takes a function and one or more collections. map passes an item from each collection, in order, to
the function and returns a lazy sequence of the results.
The function provided to map must support an arity matching the number of collections passed. Due to
this, when using more than one collection, map stops processing items when any collection runs out of
items.
mapv
([f coll])
([f c1 c2])
([f c1 c2 c3])
([f c1 c2 c3 & colls])
mapv takes a function and one or more collections. mapv passes an item from each collection, in order,
to the function and returns a vector of the results. Unlike map , mapv is not lazy.
If mapv is called with a single collection, it will use a transient vector to build the result for efficiency.
If mapv is called with multiple collections, it will first call map on them and then turn the result into a
vector (using into [] ).
The function provided to mapv must support an arity matching the number of collections passed. Due to
this, when using more than one collection, mapv stops processing items when any collection runs out of
items, like map .
conj is short for "conjoin". As the name implies, conj returns the collection with those arguments
added.
Adding items to a collection occurs at different places depending on the concrete type of collection.
List addition occurs at the beginning of the list. This is because accessing the head of the list is a constant
time operation, and accessing the tail requires traversal of the entire list.
(conj '(1 2) 3)
;; ⇒ (3 1 2)
Vectors have constant time access across the entire data structure. `'conj' thusly appends to the end of a
vector.
(conj [1 2] 3)
[1 2 3]
Maps do not have guaranteed ordering, so the location that items are added is irrelevant. conj requires
vectors of [key value] pairs to be added to the map.
{:a 1, :b 2, :c 3, :d 4}
Sets also do not have guaranteed ordering. conj returns a set with the item added. As the concept of
sets implies, added items will not duplicate equivalent items if they are present in the set.
(conj #{1 4} 5)
#{1 4 5}
#{:a :b :c :d :e}
empty
([coll])
empty returns an empty collection of the same type as the collection provided.
(empty [1 2 3])
[]
{}
assoc
([map key val])
([map key val & kvs])
assoc takes a key and a value and returns a collection of the same type as the supplied collection with
the key mapped to the new value.
assoc is similar to get in how it works with maps, records or vectors. When applied to a map or record,
the same type is returned with the key/value pairs added or modified. When applied to a vector, a vector is
returned with the key acting as an index and the index being replaced by the value.
Since maps and records can not contain multiple equivalent keys, supplying assoc with a key/value that
exists in the one will cause assoc to return modify the key at that value in the result and not duplicate the
key.
(assoc {:a 1} :b 2)
{:a 1, :b 2}
(assoc {:a 1 :b 45 :c 3} :b 2)
{:a 1, :b 2, :c 3}
When using assoc with a vector, the key is the index and the value is the value to assign to that index in
the returned vector. The key must be <= (count vector) or a "IndexOutOfBoundsException" will occur.
assoc can not be used to add an item to a vector.
(assoc [1 2 76] 2 3) ;= [1 2 3]
[1 2 3]
;; index 5 does not exist. valid indexes for this vector are: 0, 1, 2
(assoc [1 2 3] 5 6)
Execution error.
ERROR: Error: Index 5 out of bounds [0,3]
dissoc
([map])
([map key])
([map key & ks])
dissoc returns a map with the supplied keys, and subsequently their values, removed. Unlike assoc ,
dissoc does not work on vectors. When a record is provided, dissoc returns a map. For similar
functionality with vectors, see subvec and concat .
{:a 1, :c 3}
(count "Hello")
(count [1 2 3 4 5 6 7])
(count nil)
Note that count does not return in constant time for all collections. This can be determined with
counted? . Keep in mind that lazy sequences must be realized to get a count of the items. This is often
not intended and can cause a variety of otherwise cryptic errors.
(counted? "Hello")
false
true
;; Constant time return of (count)
(counted? [1 2 3 4 5])
true
empty?
([coll])
empty? returns true if the collection has no items, or false if it has 1 or more items.
(empty? [])
true
false
Do not confuse empty? with empty . This can be a source of great confusion:
(if (empty [1 2 3]) ;; empty returns an empty seq, which is true! use empty? here
"It's empty"
"It's not empty")
"It's empty"
not-empty
([coll])
not-empty returns nil if the collection has no items. If the collection contains items, the collection is
returned.
nil
first returns the first item in the collection. first returns nil if the argument is empty or is nil.
Note that for collections that do not guarantee order like some maps and sets, the behaviour of first
should not be relied on.
:floor
(first [])
nil
rest
([coll])
rest returns a seq of items starting with the second element in the collection. rest returns an empty
seq if the collection only contains a single item.
rest should also not be relied on when using maps and sets unless you are sure ordering is guaranteed.
(1 16 -4)
(rest '(:french-fry))
()
The behaviour of rest should be contrasted with next . next returns nil if the collection only has a
single item. This is important when considering "truthiness" of values since an empty seq is still a truthy
value but nil is not.
get
([map key])
([map key not-found])
get takes an associative collection, a sequence of keys and an optional default value.
get returns the value for the specified key in a map or record, index of a vector or value in a set. If the
key is not present, get returns nil or a supplied default value.
;; index of a vector
(get [10 15 20 25] 2)
20
1
;; returns nil if key is not present
(get {:a 1 :b 2} :c)
nil
nil
get also supports a default return value supplied as the last argument.
"Not Found"
contains?
([coll key])
contains returns true if the provided key is present in a collection. contains is similar to get in that
vectors treat the key as an index. contains will always return false for lists.
true
true
;; false if index 5 does not exist
(contains? ["John" "Mary" "Paul"] 5)
false
false
;; lists are not supported. contains? won't traverse a collection for a result.
(contains? '(1 2 3) 0) ; false in ClojureScript, an exception in Clojure
false
keys
([map])
(1 2 3)
vals
([map])
(20 2 5)
(defrecord Hand [index middle ring pinky thumb])
(vals (Hand. 1 2 3 4 5))
(1 2 3 4 5)
take
([n coll])
take returns a lazy sequence starting with the first value of the collection and n sequential items after
that.
If the number of items in the collection is less than the provided number, the entire collection is returned
lazily.
drop
([n coll])
drop returns a lazy sequence starting at the nth item of the collection.
take-while
([pred coll])
take-while returns a lazy sequence of sequential items until the function returns nil/false value for that
item.
(5 4 3 2 1)
drop-while
([pred coll])
drop-while returns a lazy sequence starting at the first item in the collection that the function returns
nil/false.
filter
([pred coll])
filter returns a lazy sequence of items for which the provided predicate produces a truthy value.
Contrast to remove .
(0 2 4 6 8)
("Paul" "Rudd")
When using sets with filter , remember that if nil or false is in the set and in the collection, then
the predicate will return falsey and the item will be omitted.
(:nothing :something)
filterv
([pred coll])
filterv returns a vector of items for which the provided predicate produces a truthy value. Contrast to
remove .
[0 2 4 6 8]
["Paul" "Rudd"]
When using sets with filterv , remember that if nil or false is in the set and in the collection, then
the predicate will return falsey and the item will be omitted.
(filterv #{:nothing :something nil false} [:nothing :something :things :someone n
[:nothing :something]
keep
(keep f coll)
keep returns a lazy sequence of non-nil results of the function applied to each item in the collection in
sequence.
(true false true false true false true false true false)
(0 2 4 6 8)
("Sean" "Corfield")
remove
([pred coll])
remove returns a lazy sequence of items that return false or nil for the provided predicate. Contrast
to filter .
(1 3 5 7 9)
(:h :k :z :s)
When using sets with remove , remember that if nil or false is in the set and in the collection, then the
predicate will return itself: nil . This will cause that item to be included in the returned lazy sequence.
In this example, when nil and false are tested with the predicate, the predicate returns nil. This is because
if the item is present in the set it is returned.
(remove #{:nothing :something nil} [:nothing :something :things :someone nil fals
some
([pred coll])
some will apply a predicate to each value in a collection until a non-false/nil result is returned then
immediately return that result.
Since collections are "true" values, this makes it possible to return the first result itself rather than simply
true .
true
Since maps can be used as functions, you can use a map as a predicate. This will return the value of the
first key in the collection that is also in the map.
Sets can also be used as functions and will return the first item in the collection that is present in the set.
every?
([pred coll])
every? takes a function that accepts a single argument and a collection.
every? returns true if the predicate returns true for every item in the collection, otherwise it returns false.
true
;; set can be used to see if collection only contains items in the set.
(every? #{2 3 4} [2 3 4 2 3 4])
true
partition takes a number, an optional step, an optional padding collection and a collection. If the
padding collection is provided, a step must be provided.
partition sequentially takes a provided number of items from the collection in sequence and puts them
into lists. This lazy sequence of lists is returned.
If a step is provided, the lists in the returned lazy sequence start at offsets in the provided collection of that
number items in the list.
If a padding collection is provided, the last item in the returned lazy sequence will be padded with the
padding collection to achieve the desired partitioning size.
If there is no padding collection provided and there is not enough items to fill the last list in the returned
lazy sequence, those items will be not used.
((0 1) (2 3))
((0 1) (1 2) (2 3) (3 4))
;; divide the sequence into pairs and pad the last pair with -1:
(partition 2 2 [-1] (range 5))
((0 1) (2 3) (4 -1))
partitionv
([n coll])
([n step coll])
([n step pad coll])
partitionv is just like partition above, except it returns a lazy sequence of vectors instead of a lazy
sequence of lists.
partition-all
([n coll])
([n step coll])
partition-all sequentially takes a provided number of items from the collection in sequence and puts
them into lists. This lazy sequence of lists is returned.
If a step is provided, the lists in the returned lazy sequence start at offsets in the provided collection of that
number items in the list.
If there are not enough items to fill the last list in the returned lazy sequence, the remaining items will be
used in the last list.
((0 1) (2 3) (4))
partitionv-all
([n coll])
([n step coll])
partitionv-all is just like partition-all above, except it returns a lazy sequence of vectors instead
of a lazy sequence of lists.
filter
See: filter
filterv
See: filterv
remove
See: remove
for
See: for
map
See: map
mapv
See: mapv
remove
See: remove
empty?
See: empty
not-empty
See: not-empty
juxt returns a function that will return a vector consisting of the result of applying each of those functions
to a provided argument.
comp returns a function that will return the result of applying the rightmost function to the provided
argument, then the second rightmost function to the result of that etc.
fnil
([f x])
([f x y])
([f x y z])
fnil returns a function that replaces any nil`` arguments with the provided values. fnil` only
supports supports patching 3 arguments, but will pass any arguments beyond that un-patched.
nil
apply
([f args] [f x args] [f x y args] [f x y z args] [f a b c d & args])
"[\"Hel\" \"lo\"]"
"Hello"
([1 2 3] [1 2 3])
(2 4 6)
21
->
([x])
([x form])
([x form & more])
-> takes the first argument and inserts it as the second item in the next form, or creates a list with the
first argument as the second item. The return value of that expression is inserted as the second item in the
next form, making a list if necessary. This continues until all expressions are evaluated and the final value
is returned.
(-> 5
(inc)
(- 3)
(* 2))
;; ⇒ 6
(-> {:a 1 :b 2}
:b
(inc))
;; ⇒ 3
->>
([x])
([x form])
([x form & more])
->> takes the first argument and inserts it as the last item in the next form, or creates a list with the first
argument as the last item. The return value of that expression is inserted as the last item in the next form,
making a list if necessary. This continues until all expressions are evaluated and the final value is
returned.
(->> (range 5) ; (0 1 2 3 4)
(map inc) ; (1 2 3 4 5)
(filter even?) ; (2 4)
(reduce +)) ; (+ 2 4)
;; ⇒ 6
Associative Collections
get-in
([m ks] [m ks not-found])
get-in takes an associative collection, a sequence of keys and an optional default value.
get-in takes the first value in the sequence of keys and retrieves the value, then applies each
subsequent key to to the most recently returned value and returns the final result. If any key is not present
when evaluated then either nil, or a provided default value is returned.
28
update-in
([m [k & ks] f & args])
update-in takes an associative collection, a sequence of keys, a function and optional arguments to
supply to that function.
update-in takes the first value in the sequence of keys and retrieves the value, then applies each
subsequent key to to the most recently returned value. The function and optional arguments are applied to
the value and a new nested collection is returned with the key having the result of that function.
update-in will create new hash-maps if a key in the sequence of keys does not exist. The returned
collection will have a nested structure correlating to the provided sequence along with the result of the
function and optional arguments as the value of the final key.
assoc-in
([m [k & ks] v])
assoc-in takes the first value in the sequence of keys and retrieves the value, then applies each
subsequent key to to the most recently returned value. The final key is assigned the provided value and a
new nested collection is returned.
assoc-in will create new hash-maps if a key in the sequence of keys does not exist. The returned
collection will have a nested structure correlating to the provided sequence along with the provided value
as the value of the final key.
select-keys
([map keyseq])
select-keys returns a map containing only the entries that have a key which is also present in the
sequence of keys.
(select-keys {:a 1 :b 2 :c 3} [:a :b])
{:a 1, :b 2}
keys
See: keys
vals
See: vals
get
See: get
assoc
See: assoc
dissoc
See: dissoc
Namespace Functions
ns, require, use, import, refer
Please see the Namespace guide (/articles/language/namespaces/)
Reference Types
ref, atom, var, agent
Please see the Concurrency and Parallelism Guide (/articles/language/concurrency_and_parallelism/)
Contributors
Robert Randolph [email protected] (mailto:[email protected]) (original author) Michael Klishin
[email protected] (mailto:[email protected]) Nguyễn Hà Dương [email protected]
(mailto:[email protected])