1. Hello REPL!
Overview
In this chapter, we explain the basics of creating Clojure programs. We start by getting you familiar with the Read Eval Print Loop (REPL), where most of the experimentation happens when writing code. The REPL also allows you to explore code and documentation by yourself, so it is an excellent place to start. After the quick dive in the REPL, we describe in more detail how to read and understand simple Lisp and Clojure code, which syntax can sometimes appear unsettling. We then explore fundamental operators and functions in Clojure, which enable you to write and run simple Clojure programs or scripts.
By the end of this chapter, you will be able to use the REPL and work with functions in Clojure.
Introduction
Have you ever ended up entangled in the "spaghetti code" of an object-oriented application? Many experienced programmers would say yes, and at some point in their journey or career would reconsider the foundation of their programs. They might look for a simpler, better alternative to object-oriented programming, and Clojure is an appealing choice. It is a functional, concise, and elegant language of the Lisp family. Its core is small, and its syntax minimal. It shines because of its simplicity, which takes a trained eye to notice and ultimately understand. Employing Clojure's more sophisticated building blocks will allow you to design and build sturdier applications.
Whether you are a seasoned programmer or a novice, hobbyist or professional, C# wizard or Haskell ninja, learning a new programming language is challenging. It is, however, a highly rewarding experience that will make you an overall better programmer. In this book, you will learn by doing and will ramp up your skills quickly.
Clojure is an excellent choice of programming language to learn today. It will allow you to work efficiently using a technology built to last. Clojure can be used to program pretty much anything: from full-blown client-server applications to simple scripts or big data processing jobs. By the end of this book, you will have written a modern web application using Clojure and ClojureScript and will have all the cards in your hand to start writing your own!
REPL Basics
Welcome to the Clojure Read Eval Print Loop (REPL), a command-line interface that we can use to interact with a running Clojure program. REPL, in the sense that it reads the user's input (where the user is you, the programmer), evaluates the input by instantly compiling and executing the code, and prints (that is, displays) the result to the user. The read-eval-print three-step process repeats over and over again (loop) until you exit the program.
The dynamism provided by the REPL allows you to discover and experiment with a tight feedback loop: your code is evaluated instantly, and you can adjust it until you get it right. Many other programming languages provide interactive shells (notably, other dynamic languages such as Ruby or Python), but in Clojure, the REPL plays an exceptional and essential role in the life of the developer. It is often integrated with the code editor and the line between editing, browsing, and executing code blurs toward a malleable development environment similar to Smalltalk. But let's start with the basics.
Throughout these exercises, you may notice some mentions of Java (for example, in the stack trace in the second exercise). This is because Clojure is implemented in Java and runs in the Java Virtual Machine (JVM). Clojure can, therefore, benefit from a mature ecosystem (a battle-tested, widely deployed execution platform and a plethora of libraries) while still being a cutting-edge technology. Clojure is designed to be a hosted language, and another implementation, called ClojureScript, allows you to execute Clojure code on any JavaScript runtime (for example, a web browser or Node.js). This hosted-language implementation choice allows for a smaller community of functional programmers to strive in an industry dominated by Java, .NET Core, and JavaScript technologies. Welcome to the Clojure party, where we're all having our cake and eating it too.
Exercise 1.01: Your First Dance
In this exercise, we will perform some basic operations in the REPL. Let's get started:
- Open Terminal and type
clj
. This will start a Clojure REPL:$ clj
The output is as follows:
Clojure 1.10.1 user=>
The first line is your version of Clojure, which in this example is
1.10.1
. Don't worry if your version is different—the exercises we will go through together should be compatible with any version of Clojure.The second line displays the namespace we are currently in (
user
) and prompts for your input. A namespace is a group of things (such as functions) that belong together. Everything you create here will be in theuser
namespace by default. Theuser
namespace can be considered your playground.Your REPL is ready to read.
- Let's try to evaluate an expression:
user=> "Hello REPL!"
The output is as follows:
"Hello REPL!"
In Clojure, literal strings are created with double quotes,
""
. A literal is a notation for representing a fixed value in source code. - Let's see what happens if we type in multiple strings:
user=> "Hello" "Again"
The output is as follows:
"Hello" "Again"
We have just evaluated two expressions sequentially, and each result is printed onto separate lines.
- Now, let's try a bit of arithmetic, for example,
1 + 2
:user=> 1 + 2
The output is as follows:
1 #object[clojure.core$_PLUS_ 0xe8df99a "[email protected]"] 2
The output is not exactly what we expected. Clojure evaluated the three components, that is,
1
,+
, and2
, separately. Evaluating+
looks strange because the+
symbol is bound to a function.Note
A function is a unit of code that performs a specific task. We don't need to know more for now except that functions can be called (or invoked) and can take some parameters. A function's argument is a term that's used to design the value of a parameter, but those terms are often used interchangeably.
To add those numbers, we need to call the
+
function with the arguments1
and2
. - Call the
+
function with the arguments1
and2
as follows:user=> (+ 1 2)
The output is as follows:
3
You will soon discover that many basic operations that are usually part of a programming language syntax, such as addition, multiplication, comparison, and so on, are just simple functions in Clojure.
- Let's try a few more examples of basic arithmetic. You can even try to pass more than two arguments to the following functions, so adding 1 + 2 + 3 together would look like
(+ 1 2 3)
:user=> (+ 1 2 3) 6
- The other basic arithmetic operators are used in a similar way. Try and type the following expressions:
user=> (- 3 2) 1 user=> (* 3 4 1) 12 user=> (/ 9 3) 3
After typing in the preceding examples, you should try a few more by yourself – the REPL is here to be experimented with.
- You should now be familiar enough with the REPL to ask the following question:
user=> (println "Would you like to dance?") Would you like to dance? nil
Don't take it personally –
nil
was the value that was returned by theprintln
function. The text that was printed by the function was merely a side effect of this function.nil
is the Clojure equivalent of "null," or "nothing"; that is, the absence of meaningful value.print
(without a new line) andprintln
(with a new line) are used to print objects to the standard output, and they returnnil
once they are done. - Now, we can combine those operations and print the result of a simple addition:
user=> (println (+ 1 2)) 3 nil
A value of
3
was printed and the value ofnil
was returned by this expression.Notice how we have nested those forms (or expressions). This is how we chain functions in Clojure:
user=> (* 2 (+ 1 2)) 6
- Exit the REPL by pressing Ctrl + D. The function to exit is
System/exit
, which takes the exit code as a parameter. Therefore, you can also type the following:user=> (System/exit 0)
In this exercise, we discovered the REPL and called Clojure functions to print and perform basic arithmetic operations.
Exercise 1.02: Getting around in the REPL
In this exercise, we will introduce a few navigational key bindings and commands to help you use and survive the REPL. Let's get started:
- Start by opening the REPL again.
- Notice how you can navigate the history of what was typed earlier and in previous sessions by pressing Ctrl + P (or the UP arrow) and Ctrl + N (or the DOWN arrow).
- You can also search (case-sensitive) through the history of the commands you have entered: press Ctrl + R and then
Hello
, which should bring back theHello
Again
expression we typed earlier. If you press Ctrl + R a second time, it will cycle through the matches of the search and bring back the very first command:Hello REPL!
. If you press Enter, it will bring the expression back to the current prompt. Press Enter again and it will evaluate it. - Now, evaluate the following expression, which increments (adds 1 to) the number 10:
user=> (inc 10) 11
The returned value is 11, which is indeed 10 + 1.
*1
is a special variable that is bound to the result of the last expression that was evaluated in the REPL. You can evaluate its value by simply typing it like this:user=> *1 11
Similarly,
*2
and*3
are variables bound to the second and third most recent values of that REPL session, respectively.- You can also reuse those special variable values within other expressions. See if you can follow and type this sequence of commands:
user=> (inc 10) 11 user=> *1 11 user=> (inc *1) 12 user=> (inc *1) 13 user=> (inc *2) 13 user=> (inc *1) 14
Notice how the values of
*1
and*2
change as new expressions are evaluated. When the REPL is crowded with text, press Ctrl + L to clear the screen. - Another useful variable that's available in the REPL is
*e
, which contains the result of the last exception. At the moment, it should benil
unless you generated an error earlier. Let's trigger an exception voluntarily by dividing by zero:user=> (/ 1 0) Execution error (ArithmeticException) at user/eval71 (REPL:1). Divide by zero
Evaluating
*e
should contain details about the exception, including the stack trace:user=> *e #error { :cause "Divide by zero" :via [{:type java.lang.ArithmeticException :message "Divide by zero" :at [clojure.lang.Numbers divide "Numbers.java" 188]}] :trace [[clojure.lang.Numbers divide "Numbers.java" 188] [clojure.lang.Numbers divide "Numbers.java" 3901] [user$eval1 invokeStatic "NO_SOURCE_FILE" 1] [user$eval1 invoke "NO_SOURCE_FILE" 1] [clojure.lang.Compiler eval "Compiler.java" 7177] [clojure.lang.Compiler eval "Compiler.java" 7132] [clojure.core$eval invokeStatic "core.clj" 3214] [clojure.core$eval invoke "core.clj" 3210] [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437] [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437] [clojure.main$repl$fn__9095 invoke "main.clj" 458] [clojure.main$repl invokeStatic "main.clj" 458] [clojure.main$repl_opt invokeStatic "main.clj" 522] [clojure.main$main invokeStatic "main.clj" 667] [clojure.main$main doInvoke "main.clj" 616] [clojure.lang.RestFn invoke "RestFn.java" 397] [clojure.lang.AFn applyToHelper "AFn.java" 152] [clojure.lang.RestFn applyTo "RestFn.java" 132] [clojure.lang.Var applyTo "Var.java" 705] [clojure.main main "main.java" 40]]}
Note
Different Clojure implementations may have a slightly different behavior. For example, if you tried to divide by 0 in a ClojureScript REPL, it will not throw an exception and instead return the "infinity value":
cljs.user=> (/ 1 0)
##Inf
This is to stay consistent with the host platform: the literal number 0 is implemented as an integer in Java (and Clojure) but as a floating-point number in JavaScript (and ClojureScript). The IEEE Standard for Floating-Point Arithmetic (IEEE 754) specifies that division by 0 should return +/- infinity.
- The
doc
,find-doc
, andapropos
functions are essential REPL tools for browsing through documentation. Given that you know the name of the function you want to use, you can read its documentation withdoc
. Let's see how it works in practice. Start by typing(doc str)
to read more about thestr
function:user=> (doc str) ------------------------- clojure.core/str ([] [x] [x & ys]) With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args. nil
doc
prints the fully qualified name of the function (including the namespace) on the first line, the possible sets of parameters (or "arities") on the next line, and finally the description.This function's fully qualified name is
clojure.core/str
, which means that it is in theclojure.core
namespace. Things defined inclojure.core
are available to your current namespace by default, without you explicitly having to require them. This is because they are fundamental components for building your programs, and it would be tedious to have to use their full name every time. - Let's try to use the
str
function. As the documentation explains, we can pass it multiple arguments:user=> (str "I" "will" "be" "concatenated") (clojure.core/str "This" " works " "too") "Iwillbeconcatenated" "This works too"
- Let's inspect the documentation of the
doc
function:user=> (doc doc) ------------------------- clojure.repl/doc ([name]) Macro Prints documentation for a var or special form given its name, or for a spec if given a keyword nil
This function is in the
clojure.repl
namespace, which is also available by default in your REPL environment. - You can also look at the documentation of a namespace. As its documentation suggests, your final program would typically not use the helpers in the
clojure.repl
namespace (for instance,doc
,find-doc
, andapropos
):user=> (doc clojure.repl) ------------------------- clojure.repl Utilities meant to be used interactively at the REPL nil
- When you don't know the name of the function, but you have an idea of what the description or name may contain, you can search for it with the
find-doc
helper. Let's try and search for themodulus
operator:user=> (find-doc "modulus") nil
- No luck, but there's a catch:
find-doc
is case-sensitive, but the good news is that we can use a regular expression with thei
modifier to ignore the case:user=> (find-doc #"(?i)modulus") ------------------------- clojure.core/mod ([num div]) Modulus of num and div. Truncates toward negative infinity. nil
You don't need to know more about regular expressions for now – you don't even have to use them, but it can be useful to ignore the case when searching for a function. You can write them with the
#"(?i)text"
syntax, wheretext
is anything you want to search for.The function we were looking for was
clojure.core/mod
. - Let's make sure it works according to its documentation:
user=> (mod 7 3) 1
- Use the
apropos
function to search for functions by name, thereby yielding a more succinct output. Say we were looking for a function that transforms the case of a given string of characters:user=> (apropos "case") (clojure.core/case clojure.string/lower-case clojure.string/upper-case) user=> (clojure.string/upper-case "Shout, shout, let it all out") "SHOUT, SHOUT, LET IT ALL OUT"
Please note that this function is in the
clojure.string
namespace, which is not referred to by default. You will need to use its full name until we learn how to import and refer symbols from other namespaces.
Activity 1.01: Performing Basic Operations
In this activity, we will print messages and perform some basic arithmetic operations in the Clojure REPL.
These steps will help you complete this activity:
- Open the REPL.
- Print the message "
I am not afraid of parentheses
" to motivate yourself. - Add 1, 2, and 3 and multiply the result by 10 minus 3, which corresponds to the following
infix
notation: (1 + 2 + 3) * (10 - 3). You should obtain the following result:42
- Print the message "
Well done
!" to congratulate yourself. - Exit the REPL.
Note
The solution to this activity can be found on page 678.
Evaluation of Clojure Code
Clojure is a dialect of Lisp, a high-level programming language that was designed by John McCarthy and first appeared in 1958. One of the most distinctive features of Lisp and its derivatives, or "dialects," is the use of data structures to write the source code of programs. The unusual number of parentheses in our Clojure programs is a manifestation of this as parentheses are used to create lists.
Here, we will focus on the building blocks of Clojure programs, that is, forms and expressions, and briefly look at how expressions are evaluated.
Note
The terms "expression" and "form" are often used interchangeably; however, according to the Clojure documentation, an expression is a form type: "Every form not handled specially by a special form or macro is considered by the compiler to be an expression, which is evaluated to yield a value."
We have seen how literals are valid syntax and evaluate to themselves, for example:
user=> "Hello" "Hello" user=> 1 2 3 1 2 3
We have also learned how to invoke functions by using parentheses:
user=> (+ 1 2 3) 6
It is worth noting at this point that comments can be written with ";
" at the beginning of a line. Any line starting with ";
" will not be evaluated:
user=> ; This is a comment user=> ; This line is not evaluated
Functions are invoked according to the following structure:
; (operator operand-1 operand-2 operand-3 …) ; for example: user=> (* 2 3 4) 24
Take note of the following from the preceding example:
- The list, denoted by opening and closing parenthesis,
()
, is evaluated to a function call (or invocation). - When evaluated, the
*
symbol resolves to the function that implements the multiplication. 2
,3
, and4
are evaluated to themselves and passed as arguments to the function.
Consider the expression you wrote in Activity 1.01, Performing Basic Operations: (* (+ 1 2 3) (- 10 3))
. It can also help to visualize the expression as a tree:

Figure 1.1: Tree representation of the expression, (* (+ 1 2 3) (- 10 3))
Evaluating this expression consists of reducing the tree, starting with the offshoots (the innermost lists): (* (+ 1 2 3) (- 10 3))
becomes (* 6 7)
, which becomes 42
.
The term s-expression (or symbolic expression) is often used to designate those types of expressions. You may come across it again, so it is good to know that an s-expression is a data notation for writing data structures and code with lists, as we demonstrated previously.
So far, we have only used literal scalar types as operands to our operators, which hold one value, such as numbers, strings, Booleans, and so on. We've only used lists to invoke functions and not to represent data. Let's try to create a list that represents data but not "code":
user=> (1 2 3) Execution error (ClassCastException) at user/eval255 (REPL:1). java.lang.Long cannot be cast to clojure.lang.IFn
An exception was thrown because the first item of the list (the operator) was not a function.
There is a special syntax to prevent the list from being considered as the invocation of a function: the quote. Creating a literal list is done by adding a quotation '
, in front of it, so let's try again:
user=> '(1 2 3) (1 2 3) user=> '("a" "b" "c" "d") ("a" "b" "c" "d")
Great! By preventing the evaluation of the form, we can now write a literal representation of lists.
This concept will help us get ready for what we are going to cover next. It is, however, fascinating to notice at this point that Clojure code is made up of data structures, and our programs can generate those same data structures. "Code is data" is a famous saying in the Lisp world, and a powerful concept that allows your program to generate code (known as meta-programming). If you are new to this concept, it is worth pausing for a minute to think and admire the sheer beauty of it. We will explain meta-programming techniques in detail later when explaining macros in Chapter 11, Macros.
Basic Special Forms
So far, we have been writing code that complies with the simplest rules of evaluating Clojure code, but there are some behaviors that cannot simply be encoded with normal functions. For example, arguments that have been passed to a function will always be resolved or evaluated, but what if we do not want to evaluate all the operands of an operator? That is when special forms come into play. They can have different evaluation rules for functions when the source code is read by Clojure. For example, the special form if
, may not evaluate one of its arguments, depending on the result of the first argument.
There are a few other special forms that we will go through in this section:
when
, which can be used when we are only interested in the case of a condition being truthy (a value is truthy when considered true in the context of a Boolean expression).do
, which can be used to execute a series of expressions and return the value of the last expression.def
andlet
, which are special forms that are used to create global and local bindings.fn
anddefn
, which are special forms that are used to create functions.
All these special forms have special evaluation rules, all of which we will discover by working through the following three exercises.
Exercise 1.03: Working with if, do, and when
In this exercise, we will evaluate expressions using the if
, do
, and when
forms. Let's get started:
- Start your REPL and type in the following expression:
user=> (if true "Yes" "No") "Yes"
- The special form
if
, evaluates its first argument. If its value is truthful, it will evaluate argument2
, otherwise (else
), it will evaluate argument 3. It will never evaluate both arguments 2 and 3. - We can nest expressions and start doing more interesting things:
user=> (if false (+ 3 4) (rand)) 0.4833142431072903
In this case, the computation of
(+ 3 4)
will not be executed, and only a random number (between 0 and 1) will be returned by therand
function. - But what if we wanted to do more than one thing in our branch of the condition? We could wrap our operation with
do
. Let's see howdo
works:user=> (doc do) ------------------------- do (do exprs*) Special Form Evaluates the expressions in order and returns the value of the last. If no expressions are supplied, returns nil. Please see http://clojure.org/special_forms#do Evaluates the expressions in order and returns the value of the last. If no expressions are supplied, returns nil. nil
- To use the special form,
do
type the following expression:user=> (do (* 3 4) (/ 8 4) (+ 1 1)) 2
All the expressions before the final
(+ 1 1)
expression were evaluated, but only the value of the last one is returned. This does not look very useful with expressions that don't alter the state of the world, and so it would typically be used for side effects such as logging or any other kind of I/O (filesystem access, database query, network request, and so on).You don't have to take my word for it, so let's experiment with the side effect of printing to the Terminal:
user=> (do (println "A proof that this is executed") (println "And this too")) A proof that this is executed And this too nil
- Finally, we can combine the use of
if
anddo
to execute multiple operations in a conditional branching:user=> (if true (do (println "Calculating a random number...") (rand)) (+ 1 2)) Calculating a random number... 0.8340057877906916
- Technically, you could also omit the third argument. Bring back the previous expression in the REPL and remove the last expression, that is,
(+ 1 2)
:user=> (if true (do (println "Calculating a random number...") (rand))) Calculating a random number... 0.5451384920081613 user=> (if false (println "Not going to happen")) nil
We have a better construct available for this case: the
when
operator. Instead of combiningif
anddo
, when you are only interested in doing work in one branch of the conditional execution, usewhen
. - Type the following expression to use
when
instead of a combination ofif
anddo
:user=> (when true (println "First argument") (println "Second argument") "And the last is returned") First argument Second argument "And the last is returned"
By completing this exercise, we have demonstrated the usage of the special forms known as if
, do
, and when
. We can now write expressions that contain multiple statements, as well as conditional expressions.
Bindings
In Clojure, we use the term bindings rather than variables and assignments because we tend to bind a value to a symbol only once. Under the hood, Clojure creates variables and so you may encounter this term, but it would be preferable if you don't think of them as classic variables or values that can change. We won't use the term variable anymore in this chapter as it can be confusing. You can use def
to define global bindings and let
for local bindings.
Exercise 1.04: Using def and let
In this exercise, we will demonstrate the usage of the def
and let
keywords, which are used to create bindings. Let's get started:
- The special form
def
allows you to bind a value to a symbol. In the REPL, type the following expression to bind the value10
to thex
symbol:user=> (def x 10) #'user/x
Note
When the REPL returns
#'user/x
, it is returning a reference to the var you have just created. The user part indicates the namespace where the var is defined. The#'
prefix is a way of quoting the var so that we see the symbol and not the value of the symbol. - Evaluate the expression,
x
, which will resolve thex
symbol to its value:user=> x 10
- Technically, you can change the binding, which is fine when experimenting in the REPL:
user=> (def x 20) #'user/x user=> x 20
It is, however, not recommended in your programs because it can make it hard to read and complicate its maintenance. For now, it would be better if you just consider such a binding as a constant.
- You can use the
x
symbol within another expression:user=> (inc x) 21 user=> x 20
- Wherever
def
is invoked, it will bind the value to the symbol in the current namespace. We could try to define a local binding in ado
block and see what happens:user=> x 20 user=> (do (def x 42)) #'user/x user=> x 42
The bindings that are created by
def
have an indefinite scope (or dynamic scope) and can be considered as "global." They are automatically namespaced, which is a useful trait to avoid clashing with existing names. - If we want to have a binding available only to a local scope or lexical scope, we can use the special form
let
. Type the following expression to create a lexical binding of they
symbol:user=> (let [y 3] (println y) (* 10 y)) 3 30
let
takes a "vector" as a parameter to create the local bindings, and then a series of expressions that will be evaluated like they are in ado
block.Note
A vector is similar to a list, in the sense that they both are a sequential collection of values. Their underlying data structure is different, and we will shed light on this in Chapter 2, Data Types and Immutability. For now, you just need to know that vectors can be created with square brackets, for example,
[1 2 3 4]
. - Evaluate the
y
symbol:user=> y Syntax error compiling at (REPL:0:0). Unable to resolve symbol: y in this context
An error is thrown, that is,
Unable to resolve symbol: y in this context
, because we are now outside of thelet
block. - Type the following expression to create a lexical binding of
x
to the value3
, and see how it affects the indefinite (global) binding ofx
that we created in step 4:user=> (let [x 3] (println x)) 3 nil user=> x 42
Printing
x
yields the value3
, which means that the "global"x
symbol was temporarily overridden or "shadowed" by the lexical context in whichprintln
was invoked. - You can create multiple local bindings at once with
let
by passing an even number of items in the vector. Type the following expression to bindx
to10
andy
to20
:user=> (let [x 10 y 20] (str "x is " x " and y is " y)) "x is 10 and y is 20"
- Combine the concepts of this section and write the following expressions:
user=> (def message "Let's add them all!") #'user/message user=> (let [x (* 10 3) y 20 z 100] (println message) (+ x y z)) Let's add them all! 150
The expression spans over multiple lines to improve readability.
Exercise 1.05: Creating Simple Functions with fn and defn
The special form that's used to define functions is fn
. Let's jump right into it by creating our first function:
- Type the following expression in your REPL:
user=> (fn []) #object[user$eval196$fn__197 0x3f0846c6 "[email protected]"]
We have just created the simplest anonymous function, which takes no parameters and does nothing, and we returned an object, which is our function with no name.
- Create a function that takes a parameter named
x
and return its square value (multiply it by itself):user=> (fn [x] (* x x)) #object[user$eval227$fn__228 0x68b6f0d6 "[email protected]"]
- Remember that, in Clojure, the first item of an expression will be invoked, so we can call our anonymous function by wrapping it with parentheses and providing an argument as the second item of the expression:
user=> ((fn [x] (* x x)) 2) 4
Now this is great, but not very convenient. If we wanted our function to be reusable or testable, it would be better for it to have a name. We can create a symbol in the namespace and bind it to the function.
- Use
def
to bind the function returned by the special form,fn
, to thesquare
symbol:user=> (def square (fn [x] (* x x))) #'user/square
- Invoke your newly created function to make sure that it works:
user=> (square 2) 4 user=> (square *1) 16 user=> (square *1) 256
- This pattern of combining
def
andfn
is so common that a built-in macro was born out of necessity:defn
. Recreate the square function withdefn
instead ofdef
andfn
:user=> (defn square [x] (* x x)) #'user/square user=> (square 10) 100
Did you notice that the
x
argument was passed in a vector? We have already learned that vectors are collections, and so we can add more than one symbol to the argument's vector. The values that are passed when calling the function will be bound to the symbols provided in the vector during the function's definition. - Functions can take multiple arguments, and their bodies can be composed of multiple expressions (such as an implicit
do
block). Create a function with the namemeditate
that takes two arguments: a string,s
, and a Boolean,calm
. The function will print an introductory message and return a transformation ofs
based oncalm
:user=> (defn meditate [s calm] (println "Clojure Meditate v1.0") (if calm (clojure.string/capitalize s) (str (clojure.string/upper-case s) "!")))
Note
Editing multiline expressions in the REPL can be cumbersome. As we start creating lengthier functions and expressions that span multiple lines, it would be preferable to have a window of your favorite editor open next to your REPL window. Keep those windows side by side, edit the code in your editor, copy it to your clipboard, and paste it into your REPL.
The function body contains two main expressions, the first of which is a side effect with
println
and the second of which is theif
block, which will determine the return value. Ifcalm
istrue
, it will politely return the string capitalized (with the first character converted into uppercase), otherwise it will shout and return the string with all its characters to uppercase, ending with an exclamation mark. - Let's try and make sure that our function works as intended:
user=> (meditate "in calmness lies true pleasure" true) Clojure Meditate v1.0 "In calmness lies true pleasure" user=> (meditate "in calmness lies true pleasure" false) Clojure Meditate v1.0 "IN CALMNESS LIES TRUE PLEASURE!"
- If we call the function with only the first parameter, it will throw an exception. This is because the parameters that we have defined are required:
user=> (meditate "in calmness lies true pleasure") Execution error (ArityException) at user/eval365 (REPL:1). Wrong number of args (1) passed to: user/meditate
One last thing to end our initial tour of these functions is the
doc-string
parameter. When provided todefn
, it will allow you to add a description of your function. - Add documentation to your
square
function by adding a doc-string just before the function arguments:user=> (defn square "Returns the product of the number `x` with itself" [x] (* x x)) #'user/square
The doc-string is not only useful when browsing a project's source code – it also makes it available to the
doc
function. - Look up the documentation of your
square
function withdoc
:user=> (doc square) ------------------------- user/square ([x]) Returns the product of the number `x` with itself nil
It is important to remember that the doc-string needs to come before the function arguments. If it comes after, the string will be evaluated sequentially as part of the function body and won't throw an error. It is valid syntax, but it will not be available in the
doc
helper and other development tools.It is good practice to document the arguments with backticks,
`
, like we did with`x`
, so that development tools (such as the IDE) can recognize them.
We will take a deeper dive into functions in Chapter 3, Functions in Depth, but these few basic principles will get you a long way in terms of writing functions.
Activity 1.02: Predicting the Atmospheric Carbon Dioxide Level
Carbon dioxide (CO2) is an important heat-trapping (greenhouse) gas, currently rising and threatening life as we know it on our planet. We would like to predict future levels of CO2 in the atmosphere based on historical data provided by National Oceanic and Atmospheric Administration (NOAA):

Figure 1.2: CO2 parts per million (ppm) over the years
Note
The preceding chart was taken from https://packt.live/35kUI7L and the data was taken from NOAA.
We will use the year 2006 as a starting point with a CO2 level of 382 ppm and calculate the estimate using a simplified (and optimistic) linear function, as follows: Estimate = 382 + ((Year - 2006) * 2).
Create a function called co2-estimate
that takes one integer parameter called year
and returns the estimated level of CO2 ppm for that year.
These steps will help you complete this activity:
- Open your favorite editor and a REPL window next to it.
- In your editor, define two constants,
base-co2
andbase-year
, with the values 382 and 2006, respectively. - In your editor, write the code to define the
co2-estimate
function without forgetting to document it with the doc-string parameter. - You may be tempted to write the function body in a single line but nesting a lot of function calls decreases the readability of the code. It is also easier to reason about each step of the process by decomposing them in a
let
block. Write the body of the function usinglet
to define the local bindingyear-diff
, which is the subtraction of 2006 from theyear
parameter. - Test your function by evaluating
(co2-estimate 2050)
. You should get470
as the result. - Look up the documentation of your function with
doc
and make sure that it has been defined correctly.
The following is the expected output:
user=> (doc co2-estimate) user/co2-estimate ([year]) Returns a (conservative) year's estimate of carbon dioxide parts per million in the atmosphere nil user=> (co2-estimate 2006) 382 user=> (co2-estimate 2020) 410 user=> (co2-estimate 2050) 470
Note
The solution to this activity can be found on page 679.
Truthiness, nil, and equality
Up until now, we have been using conditional expressions intuitively, possibly on the basis of how they usually work with other programming languages. In this final section, we will review and explain Boolean expressions and the related comparison functions in detail, starting with nil
and truthiness in Clojure.
nil
is a value that represents the absence of value. It is also often called NULL
in other programming languages. Representing the absence of value is useful because it means that something is missing.
In Clojure, nil
is "falsey," which means that nil
behaves like false
when evaluated in a Boolean expression.
false
and nil
are the only values that are treated as falsey in Clojure; everything else is truthy. This simple rule is a blessing (especially if you are coming from a language such as JavaScript) and makes our code more readable and less error-prone. Perhaps it's just that Clojure was not out yet when Oscar Wilde wrote, "The truth is rarely pure and never simple."
Exercise 1.06: The Truth Is Simple
In this exercise, we will demonstrate how to work with Boolean values in conditional expressions. We will also see how to play around with the logical operators in conditional expressions. Let's get started:
- Let's start by verifying that
nil
andfalse
are indeedfalsey
:user=> (if nil "Truthy" "Falsey") "Falsey" user=> (if false "Truthy" "Falsey") "Falsey"
- In other programming languages, it is common for more values to resolve to
false
in Boolean expressions. But in Clojure, remember that onlynil
andfalse
are falsey. Let's try a few examples:user=> (if 0 "Truthy" "Falsey") "Truthy" user=> (if -1 "Truthy" "Falsey") "Truthy" user=> (if '() "Truthy" "Falsey") "Truthy" user=> (if [] "Truthy" "Falsey") "Truthy" user=> (if "false" "Truthy" "Falsey") "Truthy" user=> (if "" "Truthy" "Falsey") "Truthy" user=> (if "The truth might not be pure but is simple" "Truthy" "Falsey") "Truthy"
- If we want to know whether something is exactly
true
orfalse
, and not justtruthy
orfalsey
, we can use thetrue?
andfalse?
functions:user=> (true? 1) false user=> (if (true? 1) "Yes" "No") "No" user=> (true? "true") false user=> (true? true) true user=> (false? nil) false user=> (false? false) true
The
?
character has no special behavior – it is just a naming convention for functions that return a Boolean value. - Similarly, if we want to know that something is
nil
and not justfalsey
, we can use thenil?
function:user=> (nil? false) false user=> (nil? nil) true user=> (nil? (println "Hello")) Hello true
Remember that
println
returnsnil
, and so the last piece of output in the preceding code istrue
.Boolean expressions become interesting when they are composed together. Clojure provides the usual suspects, that is,
and
andor
. At this point, we are only interested in logicaland
and logicalor
. If you are looking to play around withbitwise
operators, you can easily find them with the(find-doc "bit-")
command.and
returns the first falsey value that it encounters (from left to right) and will not evaluate the rest of the expression when that is the case. When all the values passed toand
are truthy,and
will return the last value. - Experiment with the
and
function by passing a mix of truthy and falsey values to observe the return value that's been generated:user=> (and "Hello") "Hello" user=> (and "Hello" "Then" "Goodbye") "Goodbye" user=> (and false "Hello" "Goodbye") false
- Let's use
println
and make sure that not all the expressions are evaluated:user=> (and (println "Hello") (println "Goodbye")) Hello nil
and
evaluated the first expression, which printedHello
and returnednil
, which is falsey. Therefore, the second expression was not evaluated, andGoodbye
was not printed.or
works in a similar fashion: it will return the first truthy value that it comes across and it will not evaluate the rest of the expression when that is the case. When all the values that are passed toor
are falsey,or
will return the last value. - Experiment with the
or
function by passing a mix of truthy and falsey values to observe the return value that's generated:user=> (or "Hello") "Hello" user=> (or "Hello" "Then" "Goodbye") "Hello" user=> (or false "Then" "Goodbye") "Then"
- Once again, we can use
println
to make sure that the expressions are not all evaluated:user=> (or true (println "Hello")) true
or
evaluated the first expressiontrue
and returned it. Therefore, the second expression was not evaluated, andHello
was not printed.
Equality and Comparisons
In most imperative programming languages, the =
symbol is used for variable assignments. As we've seen already, in Clojure, we have def
and let
to bind names with values. The =
symbol is a function for equality and will return true
if all its arguments are equal. As you may have guessed by now, the other common comparison functions are implemented as functions. >
, >=
, <
, <=
, and =
are not special syntax and you may have developed the intuition for using them already.
Exercise 1.07: Comparing Values
In this final exercise, we will go through the different ways of comparing values in Clojure. Let's get started:
- First, start your REPL if it is not running yet.
- Type the following expressions to compare two numbers:
user=> (= 1 1) true user=> (= 1 2) false
- You can pass multiple arguments to the
=
operator:user=> (= 1 1 1) true user=> (= 1 1 1 -1) false
In that case, even though the first three arguments are equal, the last one isn't, so the
=
function returnsfalse
. - The
=
operator is not only used to compare numbers, but other types as well. Evaluate some of the following expressions:user=> (= nil nil) true user=> (= false nil) false user=> (= "hello" "hello" (clojure.string/reverse "olleh")) true user=> (= [1 2 3] [1 2 3]) true
Note
In Java or other object-oriented programming languages, comparing things usually checks whether they are the exact same instance of an object stored in memory, that is, their identity. However, comparisons in Clojure are made by equality rather than identity. Comparing values is generally more useful, and Clojure makes it convenient, but if you ever wanted to compare identities, you could do so by using the
identical?
function. - Maybe more surprisingly, but sequences of different types can be considered equal as well:
user=> (= '(1 2 3) [1 2 3]) true
The list
1
2
3
is equivalent to the vector1
2
3
. Collections and sequences are powerful Clojure abstractions that will be presented in Chapter 2, Data Types and Immutability. - It is worth mentioning that the
=
function can also take one argument, in which case it will always returntrue
:user=> (= 1) true user=> (= "I will not reason and compare: my business is to create.") true
The other comparison operators, that is,
>
,>=
,<
, and<=
, can only be used with numbers. Let's start with<
and>
. <
returnstrue
if all its arguments are in a strictly increasing order. Try to evaluate the following expressions:user=> (< 1 2) true user=> (< 1 10 100 1000) true user=> (< 1 10 10 100) false user=> (< 3 2 3) false user=> (< -1 0 1) true
Notice that
10
followed by10
is not strictly increasing.<=
is similar, but adjacent arguments can be equal:user=> (<= 1 10 10 100) true user=> (<= 1 1 1) true user=> (<= 1 2 3) true
>
and>=
have a similar behavior and returntrue
when their arguments are in a decreasing order.>=
allows adjacent arguments to be equal:user=> (> 3 2 1) true user=> (> 3 2 2) false user=> (>= 3 2 2) true
- Finally, the
not
operator is a useful function that returnstrue
when its argument is falsey (nil
orfalse
), andfalse
otherwise. Let's try an example:user=> (not true) false user=> (not nil) true user=> (not (< 1 2)) false user=> (not (= 1 1)) false
To put things together, let's consider the following JavaScript code:
let x = 50; if (x >= 1 && x <= 100 || x % 100 == 0) { console.log("Valid"); } else { console.log("Invalid"); }
This code snippet prints
Valid
when a number,x
, is included between 1 and 100 or ifx
is a multiple of 100. Otherwise, it printsInvalid
.If we wanted to translate this to Clojure code, we would write the following:
(let [x 50] (if (or (<= 1 x 100) (= 0 (mod x 100))) (println "Valid") (println "Invalid")))
We may have a few more parentheses in the Clojure code, but you could argue that Clojure is more readable than the imperative JavaScript code. It contains less specific syntax, and we don't need to think about operator precedence.
If we wanted to transform the JavaScript code using an "inline if," we would introduce new syntax with
?
and:
, as follows:let x = 50; console.log(x >= 0 && x <= 100 || x % 100 == 0 ? "Valid" : "Invalid");
The Clojure code would become the following:
(let [x 50] (println (if (or (<= 1 x 100) (= 0 (mod x 100))) "Valid" "Invalid")))
Notice that there is no new syntax, and nothing new to learn. You already know how to read lists, and that is all you will (almost) ever need.
This simple example demonstrates the great flexibility of lists
: the building blocks of Clojure and other Lisp languages.
Activity 1.03: The meditate Function v2.0
In this activity, we will improve the meditate
function we wrote in Exercise 1.05, Creating Simple Functions with fn and defn, by replacing the calm
Boolean argument with calmness-level
. The function will print a transformation of the string passed as a second argument based on the calmness level. The specifications of the function are as follows:
calmness-level
is a number between1
and10
, but we will not check the input for errors.- If the calmness level is strictly inferior to
5
, we consider the user to be angry. The function should return thes
string transformed to uppercase concatenated with the string ", I TELL YA!
". - If the calmness level is between
5
and9
, we consider the user to be calm and relaxed. The function should return thes
string with only its first letter capitalized. - If the calmness level is
10
, the user has reached nirvana, and is being possessed by the Clojure gods. In its trance, the user channels the incomprehensible language of those divine entities. The function should return thes
string in reverse.Hint
Use the
str
function to concatenate a string andclojure.string/reverse
to reverse a string. If you are not sure how to use them, you can look up their documentation withdoc
(for example,(doc clojure.string/reverse)
).
These steps will help you complete this activity:
- Open your favorite editor and a REPL window next to it.
- In your editor, define a function with the name
meditate
, taking two arguments,calmness-level
ands
, without forgetting to write its documentation. - In the function body, start by writing an expression that prints the string,
Clojure Meditate v2.0
. - Following the specification, write the first condition to test whether the calmness level is strictly inferior to
5
. Write the first branch of the conditional expression (thethen
). - Write the second condition, which should be nested in the second branch of the first condition (the
else
). - Write the third condition, which should be nested in the second branch of the second condition. It will check that
calmness-level
is exactly10
and return the reverse of thes
string when that is the case. - Test your function by passing a string with different levels of calmness. The output should be similar to the following:
user=> (meditate "what we do now echoes in eternity" 1) Clojure Meditate v2.0 "WHAT WE DO NOW ECHOES IN ETERNITY, I TELL YA!" user=> (meditate "what we do now echoes in eternity" 6) Clojure Meditate v2.0 "What we do now echoes in eternity" user=> (meditate "what we do now echoes in eternity" 10) Clojure Meditate v2.0 "ytinrete ni seohce won od ew tahw" user=> (meditate "what we do now echoes in eternity" 50) Clojure Meditate v2.0 nil
- If you have been using the
and
operator to find whether a number was between two other numbers, rewrite your function to remove it and only use the<=
operator. Remember that<=
can take more than two arguments. - Look up the
cond
operator in the documentation and rewrite your function to replace the nested conditions withcond
.Note
The solution to this activity can be found on page 680.
Summary
In this chapter, we discovered how to use the REPL and its helpers. You are now able to search and discover new functions and look up their documentation interactively in the REPL. We learned how Clojure code is evaluated, as well as how to use and create functions, bindings, conditionals, and comparisons. These allow you to create simple programs and scripts.
In the next chapter, we will look at data types, including collections, and the concept of immutability.