The Clojure Workshop

By Joseph Fahey , Thomas Haratyk , Scott McCaughie and 2 more
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. 1. Hello REPL!

About this book

The Clojure Workshop is a step-by-step guide to Clojure and ClojureScript, designed to quickly get you up and running as a confident, knowledgeable developer.

Because of the functional nature of the language, Clojure programming is quite different to what many developers will have experienced. As hosted languages, Clojure and ClojureScript can also be daunting for newcomers because of complexities in the tooling and the challenge of interacting with the host platforms. To help you overcome these barriers, this book adopts a practical approach. Every chapter is centered around building something.

As you progress through the book, you will progressively develop the 'muscle memory' that will make you a productive Clojure programmer, and help you see the world through the concepts of functional programming. You will also gain familiarity with common idioms and patterns, as well as exposure to some of the most widely used libraries.

Unlike many Clojure books, this Workshop will include significant coverage of both Clojure and ClojureScript. This makes it useful no matter your goal or preferred platform, and provides a fresh perspective on the hosted nature of the language.

By the end of this book, you'll have the knowledge, skills and confidence to creatively tackle your own ambitious projects with Clojure and ClojureScript.

Publication date:
January 2020
Publisher
Packt
Pages
800
ISBN
9781838825485

 

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:

  1. 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 the user namespace by default. The user namespace can be considered your playground.

    Your REPL is ready to read.

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

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

  4. 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, +, and 2, 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 arguments 1 and 2.

  5. Call the + function with the arguments 1 and 2 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.

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

  8. 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 the println 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) and println (with a new line) are used to print objects to the standard output, and they return nil once they are done.

  9. 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 of nil 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
  10. 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:

  1. Start by opening the REPL again.
  2. 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).
  3. 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 the Hello 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.
  4. 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.

  5. *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.

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

  7. Another useful variable that's available in the REPL is *e, which contains the result of the last exception. At the moment, it should be nil 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.

  8. The doc, find-doc, and apropos 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 with doc. Let's see how it works in practice. Start by typing (doc str) to read more about the str 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 the clojure.core namespace. Things defined in clojure.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.

  9. 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"
  10. 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.

  11. 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, and apropos):
    user=> (doc clojure.repl)
    -------------------------
    clojure.repl
      Utilities meant to be used interactively at the REPL
    nil
  12. 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 the modulus operator:
    user=> (find-doc "modulus")
    nil
  13. 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 the i 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, where text is anything you want to search for.

    The function we were looking for was clojure.core/mod.

  14. Let's make sure it works according to its documentation:
    user=> (mod 7 3)
    1
  15. 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:

  1. Open the REPL.
  2. Print the message "I am not afraid of parentheses" to motivate yourself.
  3. 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
  4. Print the message "Well done!" to congratulate yourself.
  5. Exit the REPL.

    Note

    The solution for this activity can be found via this link.

 

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, and 4 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))

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 and let, which are special forms that are used to create global and local bindings.
  • fn and defn, 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:

  1. Start your REPL and type in the following expression:
    user=> (if true "Yes" "No")
    "Yes"
  2. The special form if, evaluates its first argument. If its value is truthful, it will evaluate argument 2, otherwise (else), it will evaluate argument 3. It will never evaluate both arguments 2 and 3.
  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 the rand function.

  4. 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 how do 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
  5. 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
  6. Finally, we can combine the use of if and do 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
  7. 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 combining if and do, when you are only interested in doing work in one branch of the conditional execution, use when.

  8. Type the following expression to use when instead of a combination of if and do:
    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:

  1. The special form def allows you to bind a value to a symbol. In the REPL, type the following expression to bind the value 10 to the x 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.

  2. Evaluate the expression, x, which will resolve the x symbol to its value:
    user=> x
    10
  3. 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.

  4. You can use the x symbol within another expression:
    user=> (inc x)
    21
    user=> x
    20
  5. 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 a do 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.

  6. 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 the y 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 a do 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].

  7. 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 the let block.

  8. Type the following expression to create a lexical binding of x to the value 3, and see how it affects the indefinite (global) binding of x that we created in step 4:
    user=> (let [x 3] (println x))
    3
    nil
    user=> x
    42

    Printing x yields the value 3, which means that the "global" x symbol was temporarily overridden or "shadowed" by the lexical context in which println was invoked.

  9. 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 bind x to 10 and y to 20:
    user=> (let [x 10 y 20]  (str "x is " x " and y is " y))
    "x is 10 and y is 20"
  10. 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:

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

  2. 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]"]
  3. 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.

  4. Use def to bind the function returned by the special form, fn, to the square symbol:
    user=> (def square (fn [x] (* x x)))
    #'user/square
  5. Invoke your newly created function to make sure that it works:
    user=> (square 2)
    4
    user=> (square *1)
    16
    user=> (square *1)
    256
  6. This pattern of combining def and fn is so common that a built-in macro was born out of necessity: defn. Recreate the square function with defn instead of def and fn:
    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.

  7. 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 name meditate that takes two arguments: a string, s, and a Boolean, calm. The function will print an introductory message and return a transformation of s based on calm:
    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 the if block, which will determine the return value. If calm is true, 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.

  8. 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!"
  9. 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 to defn, it will allow you to add a description of your function.

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

  11. Look up the documentation of your square function with doc:
    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

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:

  1. Open your favorite editor and a REPL window next to it.
  2. In your editor, define two constants, base-co2 and base-year, with the values 382 and 2006, respectively.
  3. In your editor, write the code to define the co2-estimate function without forgetting to document it with the doc-string parameter.
  4. 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 using let to define the local binding year-diff, which is the subtraction of 2006 from the year parameter.
  5. Test your function by evaluating (co2-estimate 2050). You should get 470 as the result.
  6. 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 for this activity can be found via this link.

 

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:

  1. Let's start by verifying that nil and false are indeed falsey:
    user=> (if nil "Truthy" "Falsey")
    "Falsey"
    user=> (if false "Truthy" "Falsey")
    "Falsey"
  2. In other programming languages, it is common for more values to resolve to false in Boolean expressions. But in Clojure, remember that only nil and false 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"
  3. If we want to know whether something is exactly true or false, and not just truthy or falsey, we can use the true? and false? 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.

  4. Similarly, if we want to know that something is nil and not just falsey, we can use the nil? function:
    user=> (nil? false)
    false
    user=> (nil? nil)
    true
    user=> (nil? (println "Hello"))
    Hello
    true

    Remember that println returns nil, and so the last piece of output in the preceding code is true.

    Boolean expressions become interesting when they are composed together. Clojure provides the usual suspects, that is, and and or. At this point, we are only interested in logical and and logical or. If you are looking to play around with bitwise 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 to and are truthy, and will return the last value.

  5. 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
  6. 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 printed Hello and returned nil, which is falsey. Therefore, the second expression was not evaluated, and Goodbye 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 to or are falsey, or will return the last value.

  7. 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"
  8. 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 expression true and returned it. Therefore, the second expression was not evaluated, and Hello 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:

  1. First, start your REPL if it is not running yet.
  2. Type the following expressions to compare two numbers:
    user=> (= 1 1)
    true
    user=> (= 1 2)
    false
  3. 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 returns false.

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

  5. 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 vector 1 2 3 . Collections and sequences are powerful Clojure abstractions that will be presented in Chapter 2, Data Types and Immutability.

  6. It is worth mentioning that the = function can also take one argument, in which case it will always return true:
    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 >.

  7. < returns true 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 by 10 is not strictly increasing.

  8. <= is similar, but adjacent arguments can be equal:
    user=> (<= 1 10 10 100)
    true
    user=> (<= 1 1 1)
    true
    user=> (<= 1 2 3)
    true
  9. > and >= have a similar behavior and return true 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
  10. Finally, the not operator is a useful function that returns true when its argument is falsey (nil or false), and false 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 if x is a multiple of 100. Otherwise, it prints Invalid.

    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 between 1 and 10, 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 the s string transformed to uppercase concatenated with the string ", I TELL YA!".
  • If the calmness level is between 5 and 9, we consider the user to be calm and relaxed. The function should return the s 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 the s string in reverse.

    Hint

    Use the str function to concatenate a string and clojure.string/reverse to reverse a string. If you are not sure how to use them, you can look up their documentation with doc (for example, (doc clojure.string/reverse)).

These steps will help you complete this activity:

  1. Open your favorite editor and a REPL window next to it.
  2. In your editor, define a function with the name meditate, taking two arguments, calmness-level and s, without forgetting to write its documentation.
  3. In the function body, start by writing an expression that prints the string, Clojure Meditate v2.0.
  4. 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 (the then).
  5. Write the second condition, which should be nested in the second branch of the first condition (the else).
  6. Write the third condition, which should be nested in the second branch of the second condition. It will check that calmness-level is exactly 10 and return the reverse of the s string when that is the case.
  7. 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
  8. 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.
  9. Look up the cond operator in the documentation and rewrite your function to replace the nested conditions with cond.

    Note

    The solution for this activity can be found via this link.

 

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.

About the Authors

  • Joseph Fahey

    Joseph Fahey has been a developer for nearly two decades. He got his start in the Digital Humanities in the early 2000s. Ever since then, he has been trying to hone his skills and expand his inventory of techniques. This lead him to Common Lisp and then to Clojure when it was first introduced. As an independent developer, Joseph was able to quickly start using Clojure professionally. These days, Joseph gets to write Clojure for his day job at Empear AB.

    Browse publications by this author
  • Thomas Haratyk

    Thomas Haratyk graduated from Lille University of Science and Technology and has been a professional programmer for nine years. After studying computer science and starting his career in France, he is now working as a consultant in London, helping start-ups develop their products and scale their platforms with Clojure, Ruby, and modern JavaScript.

    Browse publications by this author
  • Scott McCaughie

    Scott McCaughie lives near Glasgow, Scotland where he works as a senior Clojure developer for

    Browse publications by this author
  • Yehonathan Sharvit

    Yehonathan Sharvit has been a software developer since 2001. He discovered functional programming in 2009. It has profoundly changed his view of programming and his coding style. He loves to share his discoveries and his expertise. He has been giving courses on Clojure and JavaScript since 2016. He holds a master's degree in Mathematics.

    Browse publications by this author
  • Konrad Szydlo

    Konrad Szydlo is a psychology and computing graduate from Bournemouth University. He has worked with Clojure for the last 8 years. Since January 2016, he has worked as a software engineer and team leader at Retailic, responsible for building a website for the biggest royalty program in Poland. Prior to this, he worked as a developer with Sky, developing e-commerce and sports applications, where he used Ruby, Java, and PHP. He is also listed in the Top 75 Datomic developers on GitHub.

    Browse publications by this author
The Clojure Workshop
Unlock this book and the full library for FREE
Start free trial