Chapter 2. A whirlwind tour

 

This chapter covers

  • Getting started with Clojure programming
  • Clojure functions
  • Program flow with Clojure
  • Clojure’s core data structures

 

In the previous chapter, you read about some powerful features of the Clojure language. You saw some code, but it probably looked a little alien. It’s now time to set that right. This chapter and the next address the basics of writing code in Clojure. This one will walk you through the fundamentals of the structure and flow of Clojure programs. It will also give an overview of the various data structures that make up the core of the language. By the end of the next chapter, you’ll be able to read most Clojure code and write your own programs.

2.1. Getting started

The best way to learn a programming language is to write some code. Before you can do that, you need a working installation of Clojure and some familiarity with the REPL. In this section, we’ll download and install the language runtime and use the REPL to communicate with Clojure. Once you’re comfortable with the basics of using the REPL, we’ll try a slightly larger program. Finally, we’ll close this section with a few more notes about Clojure syntax as it relates to comments, whitespace, case sensitivity, and so on.

2.1.1. Installing Clojure

Clojure is an open-source project hosted at github.com. Git is a distributed source code version-control system, and github.com is a popular Git hosting service. You can find Clojure at this web address: http://clojure.org/downloads. To download the source code, you’ll need to have Git installed on your computer. Once you install it, go to your workspace directory and clone the Git repository with the following command:

git clone https://github.com/clojure/clojure.git

This will download the code from the master branch into the clojure directory in your workspace. Clojure is a Java project, and it uses the Ant build system. It comes with an Ant build file, which makes it easy to build the complete project. Assuming you have Ant installed, run the following command:

ant

Running this command will leave you with an appropriate Clojure JAR file. Running Clojure is as simple as starting any other Java program:

java –jar /path/to/clojure.jar

This will drop you into the Clojure REPL. The REPL is where most Clojure programs are born and developed.

2.1.2. The Clojure REPL

Clojure programs are usually not all typed out in one go. In fact, these days, programs in most languages are written using test-driven design (TDD). This technique allows the programmer to build up a larger program from smaller units of tested code. Doing this keeps programmer productivity high because the focus is always on one piece of the program at any given time. You write the test for something, write just enough code to make the test pass, and repeat the process. This style of development also has the added benefit of leaving behind a set of regression tests that can be used later. It ensures that as the program is modified and enhanced, nothing breaks existing functionality.

Clojure code can also be written with a TDD approach; indeed, it most often is. The Clojure REPL adds a fantastic tool that allows the programmer to be even more productive than when using plain TDD. This combination of using the REPL alongside the typical TDD style results in far shorter code-test-debug cycles.

As mentioned in chapter 1, the REPL stands for the read-eval-print loop. It’s an interactive shell similar to those provided by languages such as Ruby and Python. The REPL prompt (the text behind the cursor that waits for keyboard input) is the name of the active namespace followed by the => symbol. When you first start the REPL, you will see the following prompt:

user=>

As this prompt shows, Clojure puts you into the default namespace of user. You can type Clojure code at this prompt, and it will be evaluated and the return value printed at the REPL. Here’s an example:

user=> (+ 1 2)
3

user=> (defn my-addition [operand1 operand2] (+ operand1 operand2))
#'user/my-addition

user=> (my-addition 1 2)
3

user=> (my-addition 1000000000000000000 300000000000000000000)
301000000000000000000

Functions like my-addition are usually created first in the REPL, and then tested with various inputs. Once you’re satisfied that the function works, you copy the test cases into an appropriate test file. You also copy the function definition into an appropriate source file and run the tests. At any time, you can modify the definition of the function in the REPL by redefining it, and your tests will run using the new definition. This is because the REPL is a long-running process with the various definitions in its memory. That means that functions using any such redefined functions will exhibit the new behavior.

Various editors can integrate with the REPL and provide convenient ways to evaluate code inside open editors. This kind of integration further increases the productivity of the REPL-based TDD cycle. Chapter 8 has much more detail on testing and TDD using Clojure.

A final note about the REPL: the Clojure reader accepts the stream of characters from the prompt (or any other source) and converts it into Clojure data structures. The data structures are evaluated to produce the result of the program. The Clojure printer attempts to print Clojure data structures in a format that can be read back by the reader. The reader is an almost magical entity, and we’ll use it to do some powerful things. We’ll also exploit the synergy between the reader and the printer.

Now that you’ve installed Clojure and are somewhat comfortable interacting with it via the REPL, let’s write some more code. We’ll begin with the traditional “Hello, world” program, and before ending the section, we’ll address a few more points about Clojure syntax.

2.1.3. Hello, world

Let’s get started with a simple program. To keep with tradition, we’ll examine a program that prints “Hello, world,” as shown here:

(println "Hello, world ")

OK, that was too simple. Let’s write something a little more challenging. Let’s imagine that we’re creating a website for people to share pictures of their pets. The first task we might handle is a way to let users log into the site. The function we’re going to write will check a user’s credentials. For the sake of this example, we’ll store our users in a Clojure hash map (you’ll learn about hash maps later in this chapter). We’ll use the usernames as keys, and each user’s data will itself be stored as a hash map:

(def users {"kyle" {:password "secretk" :number-pets 2}
            "siva" {:password "secrets" :number-pets 4}
            "rob" {:password "secretr" :number-pets 6}
            "george" {:password "secretg" :number-pets 8}})

Now we can write a function that will decide if credentials are correct. It will accept a username and a password and then check to see if the associated user is valid:

(defn check-login [username password]
  (let [actual-password ((users username) :password)]
    (= actual-password password)))

The function is called check-login, and it takes two arguments, username and password. It does a simple check against our database of user information. You can ignore the let form for now; it’s used to introduce the local name actual-password; we’ll visit it again in the next section on program structure. We’ll also show how to use maps and symbols toward the latter part of this chapter. Let’s try check-login at the REPL:

user=> (check-login "siva" "secrets")
true

user=> (check-login "amit" "blah")
false

So it works. Before moving on to the various topics we have planned for this chapter, let’s look at a couple of facilities provided by Clojure that can help with the learning process itself.

2.1.4. doc and find-doc

Clojure provides a useful macro called doc, which allows you to look up the documentation associated with any other function or macro. It accepts the name of the entity you’re trying to learn about. Here’s an example:

user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0.

Note that it prints not only the documentation string but also what arguments can be passed to the function or macro. Here’s another example:

user> (doc doc)
-------------------------
clojure.core/doc
([name])
Macro
  Prints documentation for a var or special form given its name

Here, note that if the symbol refers to a macro, it informs you of that. Although doc is useful for when you know the name of the function or macro you want to look up, find-doc is useful if you aren’t sure of the name.

Find-doc

The find-doc function accepts a string, which can be a regex pattern. It then finds the documentation for all functions or macros whose names or associated documentation match the supplied pattern. Here’s an example:

user> (find-doc "lazy")
-------------------------
clojure.core/concat
([] [x] [x y] [x y & zs])
  Returns a lazy seq representing the concatenation of...
-------------------------
clojure.core/cycle
([coll])
  Returns a lazy (infinite!) sequence of repetitions of...

... more results

These two forms – doc and find-doc are quite useful at the REPL when you want to quickly look up what a function does or you want to find the right options.

We’re now ready to start examining the various constructs of the Clojure language. Before we do, let’s get a few administrative points about the syntax out of the way.

2.1.5. A few more points on Clojure syntax

In chapter 1, we discussed the unique, brackets-heavy syntax that Clojure employs. We examined why it exists and what power it bestows. Before moving on to the rest of this chapter, let’s address a few more things that will come in handy when writing code.

Prefix notation

Clojure code uses prefix notation (also called polish notation) to represent function calls. For those who are new to Lisp, this definitely takes a little getting used to, especially when it comes to using math functions such +, /, *, and so on. Instead of writing 1 + 2, Clojure represents this evaluation as (+ 1 2). Prefix notation is less familiar than the mathematical form we all learned at school.

Regular functions, on the other hand, don’t have this problem. In a language such as Ruby, you’d call an add function as follows:

add(1, 2)

If you look closely, this is also prefix notation because the name of the function appears first, followed by arguments. The advantage of prefix notation for functions is that the function appears as the first symbol, everything else that follows can be treated as arguments to it. The Clojure version moves the parentheses around (and drops the unnecessary comma, because whitespace is sufficient to delimit the arguments):

add(1, 2)

In most languages, mathematical functions like addition and subtraction are special cases built into the language as operators, in order to make it possible to represent math in the more familiar in-fix notation. Clojure avoids this special case by not having any operators at all. Instead, math functions are implemented using Clojure functions. All functions work the same way, whether they’re math related or not.

By avoiding special cases and relying on the same prefix notation for all functions, Clojure maintains its regularity and gives us all the advantages that come from having no syntax. We discussed this aspect of the language in some detail in chapter 1. The main advantage we talked about was that it makes it easy to generate and manipulate code. For example, consider the regular way in which Clojure structures the conditional cond form (similar to case statements in other languages):

(cond
  (> x 0) (println "greater!")
  (= x 0) (println "zero! ")
  :default (println "neither!"))

This is a nested list, and it contains an even number of expressions that appear in pairs. The first element of each pair is a test expression, and the second is the respective value that’s returned if the test expression succeeds. Generating such a simple list is easy, especially when compared to a case statement in a language like Java.

This is the reason Clojure uses prefix notation, and I can say from experience that programmers new to this way of calling functions get used to it in no time. Now, let’s discuss another couple of aspects of writing Clojure code: whitespace and comments.

Whitespace and comments

As you’ve seen, Clojure uses parentheses (and braces and square brackets) to delimit fragments of code. Unlike languages such as Ruby and Java, it doesn’t need commas to delimit elements of a list (such as a vector or arguments passed to a function). You can use commas if you like, because Clojure treats them as whitespace and ignores them. So the following two function calls are equivalent:

(+ 1 2 3 4 5)

(+ 1, 2, 3, 4, 5)

Although Clojure ignores commas, it sometimes uses them to make things easier for the programmer to read. For instance, if you have a hash map like the following

(def a-map {:a 1 :b 2 :c 3})

and ask for its value at the REPL, the Clojure printer echoes it with commas:

{:a 1, :b 2, :c 3}

The results are easier to read, especially if you’re looking at a large amount of data. Now let’s look at comments.

Like most Lisps, single-line comments in Clojure are denoted using semicolons. To comment out a line of text, put one or more semicolons at the beginning. Here’s an example:

;;this function does addition
(defn add [x y]
  (+ x y))

As an aside, some folks use the following convention relating to comment markers. Single semicolons are used when the comment appears after some program text. Double semicolons are used, as shown previously, to comment out an entire line of text. And finally, triple semicolons are used for block comments.

Clojure provides a rather convenient macro that can be used for multiline comments. The macro is called comment, and here’s an example:

(comment
(defn this-is-not-working [x y]
 (+ x y)))

This causes the whole s-expression to be treated as a comment. Specifically, the comment macro ignores forms passed in and returns nil.

As a final note on syntax, let’s address case sensitivity of Clojure.

Case sensitivity

Most Lisps are not case sensitive. Clojure, on the other hand, is case sensitive. Most modern programming languages are case sensitive today, so this should be easy for almost everyone to get used to.

Now that we’ve covered Clojure syntax, you’re ready to learn about writing programs in the language. We’ll begin by explaining the various constructs that influence the structure of Clojure code, such as defn, let, do, loop, and so on. The next couple of sections will cover program flow control and core data structures.

2.2. Program structure

In this section, we’ll examine several constructs that are part of the Clojure language. Most of those that we discuss here are categorized as structural forms because they lend structure to the code; they set up local names, allow for looping and recursion, and the like. We’ll begin with the most fundamental aspect of structuring Clojure code, namely the function.

2.2.1. Functions

Clojure is a functional language, which means that functions are first-class citizens of the language. In other words, functions can be created dynamically, be passed as arguments to other functions, can be returned from other functions, and can be stored as values inside other data structures. Clojure functions comply with all of the above.

Programming in a language that has first-class functions is a different experience compared with programming in a language that doesn’t. This latter category includes languages such as Ruby and Java. To get started with Clojure functions, let’s see how to define functions.

Function definition

Clojure offers the convenient defn macro, which allows traditional-looking function definitions, such as the following:

(defn addition-function [x y]
  (+ x y))

In reality, the defn macro expands to a combination of calls to def and fn, both of which are special forms. Here, def creates a var with the specified name, and is bound to a new function object. This function has a body as specified in the defn form. Here’s what the equivalent expanded form looks like:

(def addition-function (fn [x y]
                         (+ x y)))

The fn special form accepts a sequence of arguments in square brackets, followed by the body of the function. The fn form can be used directly to define anonymous functions. The def form shown here assigns the function created using fn to the var addition-function.

Variable arity

In order to define functions of variable arity, parameter lists can use the & symbol. An example is the addition function from Clojure core:

[+ x y & more]

This allows + to handle any number of arguments. Functions are explained in more detail in chapter 3. Now you’ll learn about a form that helps in structuring the innards of functions themselves.

2.2.2. The let form

Consider the following function:

(defn average-pets []
  (/ (apply + (map :number-pets (vals users))) (count users)))

Don’t worry yet about all that’s going on here. Observe that the body of the function is quite a long, complex-looking line of code. Such code can take several seconds to read. It would be nice if we could break it down into pieces, in order to make the intent of the code clearer. The let form allows us to introduce locally named things into our code. Consider the following alternate implementation:

(defn average-pets []
  (let [user-data (vals users)
        number-pets (map :number-pets user-data)
        total (apply + number-pets)]
    (/ total (count users))))

Here, user-data, number-pets, and total are locally named values (they’re similar to variables, but they can’t vary because Clojure’s data structures are immutable). Now the computation is much clearer, and it’s easy to read and maintain this code.

Although this is a trivial example, you can imagine more complex use cases. Further, the let form can be used to name things that might be needed more than once in a piece of code. Indeed, you can introduce a local value computed from previously named values, within the same form, for instance:

(let [x 1
      y 2
      z (+ x y)]
  (println z))

More specifically, the let form accepts as its first argument a vector containing an even number of forms, followed by zero or more forms that get evaluated when the let is evaluated. The value of the last expression is returned.

The underscore identifier

Before moving on, it’s worth discussing the situation where you might not care about the return value of an expression. Typically, such an expression is called purely for its side effect. A trivial example is calling println. If you do this inside a let form for any reason, you’d need to specify an identifier in which to hold the return value. The code might look like this:

(defn average-pets []
  (let [user-data (vals users)
        number-pets (map :number-pets user-data)
        value-from-println (println "total  pets:" number-pets)
        total (apply + number-pets)]
    (/ total (count users))))

In this code, the only reason you create value-from-println is because the let form needs a name to bind the value of each expression. In such cases where you don’t care about the value, it’s idiomatic to use a single underscore as the identifier name. Take a look at the following:

(defn average-pets []
  (let [user-data (vals users)
        number-pets (map :number-pets user-data)
        _ (println "total  pets:" number-pets)
        total (apply + number-pets)]
    (/ total (count users))))

The underscore identifier can be used in any situation where you don’t care about the value of something. This will be even more useful when we explore Clojure’s destructuring support in the next chapter.

We’ve pretty much covered the let form. One important thing to note here is that although we’ve liberally used the term variable to mean the things we’re naming inside let forms, most of Clojure is immutable. This means they’re not true variables. We’re going to explore immutability and mutation a lot more, starting with chapter 3. For now, let’s continue with learning about the do form.

2.2.3. Side effects with do

In a pure functional language, programs are free of side effects. The only way to “do something” is for a function to compute a value and return it. Calling a function doesn’t alter the state of the world in any way. Consider the following snippet of code:

(defn do-many-things []
  (do-first-thing)
  (do-another-thing)
  (return-final-value))

In a world without state and side effects, the do-many-things function would be equivalent to this one:

(defn do-many-things-equivalent []
  (return-final-value))

The calls to do-first-thing and do-another-thing can be eliminated without change in behavior, even without knowing what they do. This is because in a stateless world without side effects, the only thing that “does something” in do-many-things is the last function call to return-final-value, which presumably computes and returns a value. In such a world, there’d be no reason to ever call a series of functions (as shown in the first example), because only the last one would ever do anything useful.

The real world is full of state, and side effects are a necessity. For example, printing something to the console or to a log file is a side effect that changes the state of the world. Storing something in a database alters the state of the world and is another example of a side effect.

In order to combine multiple s-expressions into a single form, Clojure provides the do form. It can be used for any situation as described previously where some side effect is desired and the higher-order form accepts only a single s-expression. As an example, consider the if block:

(if (is-something-true?)
  (do
    (log-message "in true branch")
    (store-something-in-db)
    (return-useful-value)))

Normally, because the consequent part of the if form accepts only a single s-expression, without the do as shown here, it would be difficult to get the true case to call all three functions (log-message, store-something-in-db, and return-useful-value).

The do form is a convenient way to combine multiple s-expressions into one. This is a common idiom in macros, and plenty of core Clojure forms are macros that accept multiple forms as parameters and combine them into one using a do. Examples are when, binding, dosync, and locking.

Now that you know how to create blocks of code using do, we’ll move on to learning about other structural constructs in the remainder of this section. First, though, let’s look at exception handling in Clojure.

2.2.4. try/catch/finally and throw

A significant part of Clojure is written in Java, and the language runtime itself runs on the JVM. This is the reason why it was a natural choice to continue to use Java exceptions as the error notification system in Clojure. It’s an aspect of transparent interoperability with existing Java code, as you’ll see in chapter 5. Meanwhile, let’s take a quick look at how to handle and also throw exceptions in Clojure code.

If an expression has the potential to throw an exception, a try/catch/finally block can be used to catch it and decide what to do with it. This is optional because Clojure doesn’t expect you to handle checked exceptions as Java does. Here’s an example, a modification of our previous function that calculates the average number of pets our users own:

(defn average-pets [users]
  (let [user-data (vals users)
        number-pets (map :number-pets user-data)
        total (apply + number-pets)]
    (/ total (count users))))

Now imagine that we had no users in our system yet:

(def no-users {})

If we try calling average-pets with no-users, we’ll get an exception:

user> (average-pets no-users)
; Evaluation aborted.
Divide by zero
  [Thrown class java.lang.ArithmeticException]

Normally, we’d check for the empty list, but for illustration purposes, let’s add a try/catch block:

(defn average-pets [users]
  (try
   (let [user-data (vals users)
        number-pets (map :number-pets user-data)
        total (apply + number-pets)]
    (/ total (count users)))
   (catch Exception e
     (println "Error!")
     0)))

When we now attempt the same thing as before, we get this:

user=> (average-pets no-users)
Error!
0

The general form of using try/catch/finally is straightforward:

(try expr* catch-clause* finally-clause?)

The form accepts multiple expressions as part of the try clause and multiple catch clauses. The finally clause is optional. The expressions passed to the try clause are evaluated one by one, and the value of the last is returned. If any of them generate an exception, the appropriate catch clause is executed based on the type (class) of the exception, and the value of that is then returned. The optional finally clause is always executed for any side effects that need to be guaranteed.

Exceptions can be thrown as easily using the throw form. In any place where you wish to throw an exception, you can do something like the following:

(throw (Exception. "this is an error!"))

throw accepts a Java exception object, so any kind of exception can be thrown using it.

That covers the basics of using the try/catch/finally form as well as throwing exceptions. This isn’t a commonly used feature of the Clojure language, because it doesn’t force you to handle or declare checked exceptions as Java does. There are several helper macros that take care of many situations where you might need to use this form. You’ll see this more in chapter 5. In the meantime, our last stop in this section will be a brief exploration of reader macros.

2.2.5. Reader macros

We discussed the Clojure reader in chapter 1, and you saw that it converts program text into Clojure data structures. It does this by recognizing that characters such as parentheses, braces, and the like are special and that they form the beginning (and ending) of lists, hash maps, and so on. These rules are built into the reader.

Other characters are special also, because they signal to the reader that the form that follows them should be treated in a special way. In a sense, these characters extend the capability of the reader, and they’re called reader macros. The simplest (and most traditional) example of a reader macro is the comment character (;). When the reader encounters a semicolon, it treats the rest of that line of code as a comment and ignores it.

The quotation reader macro is another simple example. Consider the following line of code:

(add 1 2 3 4 5)

This is a call to the add function, with the remaining symbols (numbers) as the parameters. Any list like this is treated the same way; the first element should resolve to a function and the remaining elements are treated as arguments to that function. If you want to avoid this, you can quote it using the quote form:

(quote (add 1 2 3 4 5))

This causes the whole list (including the first add symbol) to be treated as is (a list of symbols) and be returned. The reader macro (' is equivalent to using the quote form:

'(add 1 2 3 4 5)

Table 2.1 shows the available reader macros in Clojure.

Table 2.1. Clojure’s reader macros and their descriptions

Reader macro character

Description of reader macro

Quote (') Quotes the form following it
Character () Yields a character literal
Comment (;) Single-line comment
Meta (^) Associates metadata for the form that follows
Deref (@) Dereferences the agent or ref that follows
Dispatch (#) #{} Constructs a set
  #" Constructs a regex pattern
  #^ Associates metadata for the next form (deprecated)
  #'var quote—resolves the var that follows
  #() Constructs an anonymous function
  #_ Skips the following form
Syntax quote (`) Syntax quote, used in macros to render s-expressions
Unquote (~) Unquotes forms inside syntax-quoted forms
Unquote splice (~@) Unquotes a list inside a syntax form, but inserts the elements of the list without the surrounding brackets

You’ll learn about each of these reader macros in the relevant section in the book. For instance, we’ll use the last three quite heavily in the chapter about macros.

Reader macros are implemented as entries in a read table. An entry in this table is essentially a reader macro character associated with the macro function that describes how the form that follows is to be treated. Most Lisps expose this read table to the programmer, allowing them to manipulate it or add new reader macros. Clojure doesn’t do this, and so you can’t define your own reader macros.

In this section, you saw various structural constructs provided by Clojure. In the next section, you’ll see forms that control the execution flow of Clojure programs.

2.3. Program flow

Clojure is a simple language to learn, with few special forms, and indeed few constructs that control the flow of execution. In this section, we’ll begin with conditional program execution with the if special form and other macros built on the if form, and then we’ll look at various functional constructs that allow for looping and working on sequences of data. Specifically, we’ll consider loop/recur, followed by a few macros that use loop/recur internally to make it convenient to process sequences. We’ll close this chapter with a few higher-order functions that apply other functions to sequences of data.

2.3.1. Conditionals

A conditional form is one that causes Clojure to either execute or not execute associated code. The most basic example of this is the if form. In Clojure, the general form of if looks like this:

(if test consequent alternative)

This shows that the if form accepts a test expression, which is evaluated to determine what to do next. If the test is true, the consequent is evaluated. If the test is false, and if an alternative form is provided, then it is evaluated instead (else nil is returned). Because the consequent and alternative clauses of the if form can only be a single s-expression, you can use the do form to have it do multiple things.

if is a special form, which means that the Clojure language implements it internally as a special case. In a language that provides the if special form and a macro system, all other conditional forms can be implemented as macros, which is what Clojure does. Let’s visit a few such macros.

If-not

The if-not macro does the inverse of what the if special form does. The general structure of this macro is

(if-not test consequent alternative)

Here, if the test is false, the consequent is evaluated, else if it is true and the alternative is provided, it is evaluated instead.

Cond

cond is like the case statement of Clojure. The general form looks like the following:

(cond & clauses)

Here’s a trivial example of using cond:

(defn range-info [x]
  (cond
    (< x 0) (println "Negative!")
    (= x 0) (println "Zero!")
    :default (println "Positive!")))

As you can see, the clauses are pairs of expressions, each of the form test consequent. Each test expression is evaluated in sequence, and when one returns true, the associated consequent is evaluated and returned. If none returns true, we can pass in something that works as a true value (for example, the :default symbol as shown below), the associated consequent is evaluated and returned instead.

When

Here’s the general form of the when macro:

(when test & body)

This convenient macro is an if (without the alternative clause), along with an implicit do. This allows multiple s-expressions to be passed in as the body. Here’s how it might be used:

(when (some-condition?)
  (do-this-first)
  (then-that)
  (and-return-this))

Note that there’s no need to wrap the three functions in the body inside a do, because the when macro takes care of this. You’ll find this a common pattern, and it’s a convenience that most macros provide to their callers.

When-not

when-not is the opposite of when, in that it evaluates its body if the test returns false (or nil). The general form looks similar to that of when:

(when-not test & body)

These are some of the many forms that allow programs to handle different kinds of conditional situations. Except for the if special form, they’re all implemented as macros, which also implies that the programmer is free to implement new ones, suited to the domain of the program. In the next section, you’ll see a little more detail about writing test expressions using logical functions.

Logical functions

Any expression that evaluates to true or false can be used for the test expression in all the previously mentioned conditional forms. In order to write compound test expressions, Clojure provides some logical functions. Let’s examine the logical and first. Here’s the general form:

(and x & next)

It implies that and accepts one or more forms (zero or more; calling and without any arguments returns true). It evaluates each in turn, and if any returns nil or false, and returns that value. If none of the forms return false or nil, then and returns the value of the last form. and short circuits the arguments by not evaluating the remaining if any one returns false. Here’s an example:

(if (and (is-member? user)
         (has-special-status? user))
  (welcome-warmly user))

or works in the opposite way. It also accepts one or more forms (zero or more, calling or without any arguments returns nil) and evaluates them one by one. If any returns a logical true, it returns it as the value of the or. If none return a logical true, then the value of the last one is returned. or also short-circuits its arguments. Here’s an example:

(if (or (never-logged-in? user) (has-no-expenses? user))
  (email-encouragement user))

Another point of interest is that both and and or are also macros. This means that the Clojure language doesn’t provide these as part of its core, but instead they’re part of the standard library. It also means that we can write our own macros that behave like and or or and they would be indistinguishable from the language. I know we keep saying this, but you’ll see what this means in chapter 7.

Finally, Clojure provides a not function that inverts the logical value of whatever is passed in as an argument. Here’s an example:

(if (not (thrifty? user))
 (email-savings user))

As a relevant side note, Clojure provides all the usual comparison and equality functions. Examples are <, <=, >, >=, and =. They all work the way you’d expect them to, with an additional feature: they take any number of arguments. The < function, for instance, checks to see if the arguments are in increasing order. The = function is the same as Java’s equals, but it works for a wider range of objects including nil, numbers, and sequences. Note that it’s a single = symbol and not ==, which is commonly used in many programming languages.

These logical functions are sufficient in order to create compound logical expressions from simple ones. Our next stop in this section is going to be iterations—not strictly the kind supported by imperative languages such as C++ and Java, but the functional kind.

2.3.2. Functional iteration

Most functional languages don’t have traditional iteration constructs like for because typical implementations of for require mutation of the loop counter. Instead, they use recursion and function application to process lists of things. We’ll start this section by looking at the familiar while form, followed by examining Clojure’s looping construct of loop/recur. Then we’ll examine a few convenient macros such as doseq and dotimes, which are built on top of loop/recur.

While

Clojure’s while macro works in a similar fashion to those seen in imperative languages such as Ruby and Java. The general form is as follows:

(while test & body)

An example is

(while (request-on-queue?)
  (handle-request (pop-request-queue)))

Here, requests will continue to be processed as long as they keep appearing on the request queue. The while loop will end if request-on-queue? returns a value either false or nil, presumably because something else happened elsewhere in the system. Note that the only way for a while loop to end is for a side effect to cause the test expression to return false.

Now, let’s move on to another looping construct, one that’s somewhat different from imperative languages, because it relies on what appears to be recursion.

Loop/recur

Clojure doesn’t have traditional for loops for iteration; instead, programs can achieve similar behavior through the use of higher-level functions such as map and other functions in the sequence library. The Clojure version of iterative flow control is loop and the associated recur. Here’s an example of calculating the factorial of a number n using loop/recur:

(defn fact-loop [n]
  (loop [current n fact 1]
    (if (= current 1)
      fact
      (recur (dec current) (* fact current)))))

Here’s the general form of the loop form:

(loop bindings & body)

loop sets up bindings that work exactly like the let form does. In this example, [current n fact 1] works the same way if used with a let form: current gets bound to the value of n, and fact gets bound to a value of 1. Then it executes the supplied body inside the lexical scope of the bindings. In this case, the body is the if form.

Now let’s talk about recur. It has similar semantics as the let form bindings:

(recur bindings)

The bindings are computed, and each value is bound to the respective name as described in the loop form. Execution then returns to the start of the loop body. In this example, recur has two binding values, (dec current) and (* fact current), which are computed and rebound to current and fact. The if form then executes again. This continues until the if condition causes the looping to end by not calling recur anymore.

recur is a special form in Clojure, and despite looking recursive, it doesn’t consume the stack. It’s the preferred way of doing self-recursion, as opposed to a function calling itself by name. The reason for this is that Clojure currently doesn’t have tail-call optimization, though it’s possible that this will be added at some point in the future if the JVM were to support it. recur can be used only from tail positions of code, and if an attempt is made to use it from any other position, the compiler will complain. For instance, this will cause Clojure to complain:

(defn fact-loop-invalid [n]
  (loop [current n fact 1]
    (if (= current 1)
      fact
      (recur (dec current) (* fact current)))
    (println "Done, current value:" current)))

The specific error you will see is

Can only recur from tail position
  [Thrown class java.lang.UnsupportedOperationException]

This will tip you off that you have a recur being used from a non-tail position of loop, and such errors in code are easy to fix.

As you’ve seen, loop/recur is easy to use. recur is more powerful and can cause execution to return to any recursion point. Recursion points can be set up, as you saw in the example, by a loop form or by a function form (enabling you to create self-recursive functions). You’ll see the latter in action in the next chapter. Now let’s look at a few macros that Clojure provides that make it easy to work with sequences without having to directly use loop/recur.

Doseq, dotimes

Imagine that you have a list of users and you wish to generate expense reports for each user. You could use the looping construct from the previous section, but instead there’s a convenient way to achieve the same effect in the following dispatch-reporting-jobs function:

(defn run-report [user]
  (println "Running report for" user))

(defn dispatch-reporting-jobs [all-users]
  (doseq [user all-users]
    (run-reports user)))

Here, the form of interest is doseq. The simplest form accepts a vector containing two terms, where the first term is a new symbol, which will be sequentially bound to each element in the second term (which must be a sequence). The body will be executed for each element in the sequence. In this case, dispatch-reporting-jobs will call run-reports for each user present in the sequence all-users. dotimes is similar. It’s a convenience macro that accepts a vector containing a symbol and a number n, followed by the body. The symbol is set to numbers from 0 to (n – 1), and the body is evaluated for each number. Here’s an example:

(dotimes [x 5]
    (println "Factorial of " x "is =" (factorial x)))

This will calculate and print the factorials of the numbers 0 through 4.

Despite the convenience of these macros, they’re not used as much as you’d imagine, especially if you’re coming from an imperative background. In Clojure, the most common pattern of computing things from lists of data is using higher-level functions such as map, filter, and reduce. We’ll look at these briefly in the remainder of this section.

Map

The simplest use of map accepts a unary function and a sequence of data elements. A unary function is a function that accepts only one argument. map applies this function to each element of the sequence and returns a new sequence that contains all the returned values. Here’s an example:

(defn find-daily-totals [start-date end-date]
  (let [all-dates (dates-between start-date end-date)]
    (map find-total-expenses all-dates)))

Here, the last line is where map collects the values received from calling find-total-expenses on each date in all-dates. In other languages, this would have required much more code: an iteration block, a list that collects the return values, and a condition that checks to see if the list is exhausted. A single call to map does all this. Even though this is a common way of using map, it’s even more general than this. map accepts a function that can take any number of arguments, along with the same number of sequences. It collects the result of applying the function to corresponding elements from each sequence. If the sequences are of varying lengths, map will only work through the shortest one.

Filter

filter does something similar to map—it collects values. But it accepts a predicate function and a sequence and returns only those elements of the sequence that return a logically true value when the predicate function is called on them. Here’s an example:

(defn non-zero-expenses [expenses]
  (let [non-zero? (fn [e] (not (zero? e)))]
    (filter non-zero? expenses)))

Or here’s a more succinct alternative:

(defn non-zero-expenses [expenses]
  (filter pos? expenses))

pos? is a function that checks to see if the supplied argument is greater than zero.

For several kinds of calculations, you’ll need to operate on only those expenses that aren’t zero. non-zero-expenses is a function that removes all but such values, and it does so in one line of code (three words!).

Reduce

The simplest form of reduce is a high-level function that accepts a function of arity 2 and a sequence of data elements. The function is applied to the first two elements of the sequence, producing the first result. The same function is then called again with this result and the next element of the sequence. This then repeats with the following element, and so on.

Let’s write the factorial function using reduce:

(defn factorial [n]
  (let [numbers (range 1 (+ n 1))]
    (reduce * numbers)))

range is a Clojure function that returns a list of numbers starting from the first argument (inclusive) to the second argument (exclusive). This is why numbers is computed by calling range with 1 and (+ n 1). The rest is easy; you reduce the sequence using the multiply (*) function.

Let’s examine how this works when factorial is called with 5. numbers is set to the result of calling range on 1 and 6, which is the sequence of the numbers 1, 2, 3, 4, and 5. This sequence is what reduce operates on, along with the multiplication function. The result of multiplying 1 and 2 (which is 2) is multiplied by 3 (resulting in 6). That is then multiplied by 4 (resulting in 24), which is finally multiplied by 5, resulting in 120.

reduce is a powerful function, and as shown here, it accomplishes in a single line of code what might require several lines in other languages.

For

What book can be complete without mentioning for in the context of iteration? We said earlier that few functional languages have a traditional for construct. Clojure does have for, but it isn’t quite like what you might be used to. Similar to doseq, for is used for list comprehensions, which is a syntactic feature that allows sequences to be constructed out of existing ones. The general form of the for construct follows:

(for seq-exprs body-expr)

seq-exprs is a vector specifying one or more binding-form/collection-expr pairs. body-expr can use the bindings set up in seq-exprs to construct each element of the list. Consider the following example that generates a list of labels for each square on a chessboard:

(defn chessboard-labels []
  (for [alpha "abcdefgh"
       num (range 1 9)]
    (str alpha num)))

When called, this returns a list with all 64 labels:

user=> (chessboard-labels)
("a1" "a2" "a3" "a4" "a5" ... "h6" "h7" "h8")

The for seq-exprs can take modifiers :let, :when, and :while. To see an example of :when in use, let’s first consider a function that checks to see if a number is prime:

(defn prime? [x]
  (let [divisors (range 2 (inc (int (Math/sqrt x))))
          remainders (map #(rem x %) divisors)]
    (not (some zero? remainders))))

Although there are more efficient ways to test for a prime number, this implementation will suffice for our example. Now let’s use for to write a function primes-less-than, which returns a list of all primes between 2 and the number passed in:

(defn primes-less-than [n]
  (for [x (range 2 (inc n))
           :when (prime? x)]
    x))

Notice how we specify a condition in the for form using the :when option. Let’s test this function:

user=> (primes-less-than 50)
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)

Let’s look at another, slightly more complex example. Let’s use the prime? function to find all pairs of numbers under, say, a number like 5, such that the sum of each is prime. Here it is:

(defn pairs-for-primes [n]
  (let [z (range 2 (inc n))]
    (for [x z y z :when (prime? (+ x y))]
      (list x y))))

Let’s test it out:

user=> (pairs-for-primes 5)
((2 3) (2 5) (3 2) (3 4) (4 3) (5 2))

As you can see, Clojure’s for is a powerful construct, and it can be used to create arbitrary lists. A great advantage of this feature is that it’s almost declarative. For instance, the code in pairs-for-primes reads almost like a restatement of the problem itself.

Our next stop isn’t strictly about program flow but about a couple of macros that are useful in writing other functions and macros.

2.3.3. The threading macros

You’re going to learn a lot about macros in this book, starting with an introduction to them in chapter 7. From a developer point of view, there are several macros that are extremely useful. You’ve seen several already, and in this section you’ll see two more, which make writing code a lot more convenient and result in more readable code as well. They’re called threading macros.

Thread-first

Imagine that you need to calculate the savings that would be available to a user several years from now based on some amount they invest today. You can use the formula for compound interest to calculate this:

final-amount = principle * (1 + rate/100) ^ time-periods

You can write a function to calculate this:

(defn final-amount [principle rate time-periods]
  (* (Math/pow (+ 1 (/ rate 100)) time-periods) principle))

You can test that it works by calling it at the REPL:

user> (final-amount 100 20 1)
120.0

user> (final-amount 100 20 2)
144.0

This is fine, but the function definition is difficult to read, because it’s written inside out, thanks to the prefix nature of Clojure’s syntax. This is where the thread-first macro (called ->) helps, as shown in the following code:

(defn final-amount-> [principle rate time-periods]
  (-> rate
      (/ 100)
      (+ 1)
      (Math/pow time-periods)
      (* principle)))

It works the same, and you can confirm this on the REPL:

user> (final-amount-> 100 20 1)
120.0

user> (final-amount-> 100 20 2)
144.0

What the thread-first macro does is to take the first argument supplied and place it in the second position of the next expression. It’s called thread-first because it moves code into the position of the first argument of the following form. It then takes the entire resulting expression and moves it into the second position of the following expression, and so on, until all expressions are exhausted. So when the macro expands in the case of our final-amount-> function, the form looks like this:

(* (Math/pow (+ 1 (/ rate 100)) time-periods) principle)

To be more accurate, the call to Java’s Math/pow is also expanded, but we’ll explore that in chapter 5. For now, it’s enough to see that the expanded form is exactly like the one we manually defined in final-amount earlier. The advantage is that final-amount-> is much easier to write and to read.

In the next section, we’ll examine a related macro, called thread-last.

Thread-last

The thread-last macro (named ->>) is a cousin of the thread-first macro. Instead of taking the first expression and moving it into the second position of the next expression, it moves it into the last place. It then repeats the process for all the expressions provided to it. Let’s examine a version of the factorial function again:

(defn factorial [n]
  (apply *
          (range 1
                 (+ 1
                    n))))

This is also written in the inside-out syntax, and it isn’t immediately obvious what the sequence of operations is. Here’s the same function rewritten using the ->> macro:

(defn factorial->> [n]
  (->> n
       (+ 1)
       (range 1)
       (apply *)))

You can check that it works by testing it at the REPL:

user=> (factorial->> 5)
120

This macro expands our factorial->> function to

(apply * (range 1 (+ 1 n)))

This ensures that it works the same way as factorial defined previously. The main advantage of this macro (similar to the -> macro) is that it lets developers focus on the sequence of operations, rather than ensuring they’re writing the nested expressions correctly. It’s also easy to read and maintain the resulting function.

In this section, you saw various ways to control the execution flow of Clojure programs. We started off with conditionals and explored the associated logical functions. We then addressed the idea of looping—not directly as imperative for loops do in other languages, but through a recursive form and through higher-order functions. Armed with this knowledge, you could write a lot of code without ever missing imperative constructs.

2.4. Clojure data structures

In this section, we’re going to quickly explore the various built-in data types and data structures of Clojure. We’ll start with the basic characters, strings, and so on and end with Clojure sequences.

2.4.1. nil, truth, and falsehood

You’ve seen these in action in the last several pages, so let’s run a quick recap. Clojure’s nil is equivalent to null in Java and nil in Ruby. It means “nothing.” Calling a function on nil may lead to a NullPointerException, although core Clojure functions try to do something reasonable when operating on nil.

Boolean values are simple. Everything other than false and nil is considered true. There’s an explicit true value, which can be used when needed.

2.4.2. Chars, strings, and numbers

Clojure characters are Java characters. Clojure has a reader macro, the backslash, which can be used to denote characters, like a or g.

Clojure strings are Java strings. They’re denoted using double quotes (because single quote is a reader macro, which as you saw earlier, means something else entirely). For this reason, it’s useful to know the API provided by the Java String class. Some examples are

(.split "clojure-in-action" "-")

and

(.endsWith "program.clj" ".clj")

both of which return what you’d expect. Note the leading periods in .split and .endsWith. This is Clojure syntax for calling a Java method, and chapter 5 focuses entirely on Java interop.

Clojure numbers are implemented in a rather elegant fashion. They’re Java-boxed numbers that ultimately derive from the java.lang.Number class. BigInteger, BigDecimal, Double, Float, and the like are all part of that hierarchy.

Clojure does define one more type of number: the ratio. Ratios are created when two integers are divided such that they can’t be reduced any further. Here’s an example: executing the code (/ 4 9) returns a ratio object 4/9. If instead of dividing such integers, one of the numbers is a Double, then the result is also a Double. As an example, evaluating (/ 4.0 9) returns 0.4444444444444444.

One final thing of interest: Clojure converts Integers that exceed their capacity to Longs. Further, when Longs, in turn, exceed their capacity, they get converted into BigIntegers.

We’ve covered the basic data types in Clojure, and these are probably familiar to programmers from most backgrounds. We’re now going to examine a few that are somewhat unique to the Clojure language. We’ll begin with keywords and symbols and then move on to sequences.

2.4.3. Keywords and symbols

Keywords are similar to those in languages like Ruby (where they’re called symbols). Some examples of keywords are :first-name and :last-name. Keywords are symbolic identifiers that always evaluate to themselves. A typical use of keywords is as keys inside hash maps, but they can be used anywhere a unique value is needed.

Keywords have a rather convenient property—they’re also functions. They operate on hash maps by looking themselves up in them. We’ll say more about this in the next chapter.

Clojure symbols are identifiers that evaluate to something they name. They’re used for pretty much everything in Clojure. For instance, consider s-expressions where function parameter names, local names created inside let bindings, vars that represent function names or global constants, and so on are all represented symbolically using Clojure symbols. Symbols can have metadata (you’ll learn about metadata in the next chapter).

Symbols, like keywords, also have the interesting property of being functions. They accept hash maps and look themselves up in them. You’ll learn about these as well in the next chapter. In the chapter on macros, we’ll manipulate symbols inside s-expressions in order to manipulate and transform code. In the meantime, we’ll look at Clojure sequences, which represent the language’s fresh take on Lisps’ core data structure: the list.

2.4.4. Sequences

In one sense, Lisp was created to process lists. It’s not an overstatement, then, to say that the list is a core data structure in the language (to be more specific, the core data structure is the cons cell). Other, more complicated data structures can be created in Lisp by building on the simple list. In general, this isn’t a problem. Because most Lisps implement the list in a hardwired way (using the cons cell), the usefulness of many Lisp functions is limited to operating on such lists.

Clojure avoids this problem in an elegant way. Instead of providing a concrete implementation of a list data structure, it provides an abstract interface called ISeq. Any concrete implementation of ISeq is a valid sequence, and all of Clojure’s power can be brought to bear on it. This includes library functions, special forms, and macros. Because most of the sequence library in Clojure is lazy, user-defined data structures that implement ISeq can also benefit from this laziness. Examples of sequences are lists, vectors, and hash maps.

The ISeq interface provides three functions: first, rest, and cons. Here’s how first and rest work:

user=> (first [1 2 3 4 5])
1

user=> (rest [1 2 3 4 5])
(2 3 4 5)

They’re both pretty straightforward. first returns the first element of the sequence, whereas rest returns the sequences without the first element. cons creates new sequences given an element and an existing sequence. cons, which is short for construct, is so named for historical reasons. It works as follows:

user=> (cons 1 [2 3 4 5])
(1 2 3 4 5)

These three functions, and indeed many others, all work with sequences, aka the ISeq interface. In the remainder of this section, we’ll examine the most commonly used data structures that implement this interface. As we discuss each of the following, it will help to keep in mind that all core data structures in Clojure are immutable.

Lists

Lists are the basic data structure of Clojure. Lists are plain collections of symbols and expressions, surrounded by parentheses. They can be created using the list function:

user=> (list 1 2 3 4 5)
(1 2 3 4 5)

The conj function can be used to add values to a list:

user=> (conj (list 1 2 3 4 5) 6)
[1 2 3 4 5 6]

Because lists are immutable, what conj does is return a new list with the additional element. You can check if a data structure is a list by using the list? predicate:

user=> (let [a-list (list 1 2 3 4 5)]
          (list? a-list))
true
Lists are special

As you learned earlier, Clojure code is represented using Clojure data structures. The list is special because each expression of Clojure code is a list. The list may contain other data structures such as vectors, but the list is the primary one.

In practice, this implies that lists are treated differently. Clojure assumes that the first symbol appearing in a list represents the name of a function (or a macro). The remaining expressions in the list are considered arguments to the function. Here’s an example:

(+ 1 2 3)

This list contains the symbol for plus (which evaluates to the addition function), followed by symbols for numbers representing one, two, and three. Once the reader reads and parses this, the list is evaluated by applying the addition function to the numbers 1, 2, and 3. This evaluates to 6, and this result is returned as the value of the expression (+ 1 2 3).

This has another implication. What if you wanted to define three-numbers as a list containing the numbers 1, 2, and 3? Let’s try that:

user=> (def three-numbers (1 2 3))
; Evaluation aborted.
java.lang.Integer cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]

The reason for this error is that Clojure is trying to treat the list (1 2 3) the same way as it treats all lists. The first element is considered a function, and here the integer 1 isn’t a function. What we want here is for Clojure to not treat the list as code. We want to say, “This list is not code, so don’t try to apply normal rules of evaluation to it.” As you’ve seen, you can do this by using quotation, and the quote form does exactly this:

user=> (def three-numbers (quote (1 2 3)))
#'user/three-numbers
user=> three-numbers
(1 2 3)

You also saw the reader macro for quote, which is a shortcut to using quote. Here it is in action again:

user=> (def three-numbers '(1 2 3))
#'user/three-numbers

The ability to quote lists is important, as you’ll see when we generate code using the macro system. Now, having seen the most basic of the various built-in implementations of ISeq, let’s examine a similar one, the vector.

Vectors

Vectors are like lists, except for two things: they’re denoted using square brackets, and they’re indexed by numbers. Vectors can be created using the vector function or literally using the square bracket notation:

user=> (vector 10 20 30 40 50)
[10 20 30 40 50]

user=> (def the-vector [10 20 30 40 50])
#'user/the-vector

Being indexed by numbers means that you have random access to the elements inside a vector. The functions that allow you to get these elements are get and nth. If the-vector is a vector of several elements, the following is how you’d use these functions:

user=> (get the-vector 2)
30

user=> (nth the-vector 2)
30

user=> (get the-vector 10)
nil

user=> (nth the-vector 10)
; Evaluation aborted.
[Thrown class java.lang.IndexOutOfBoundsException]

As shown here, the difference between nth and get is that nth throws an exception if the value is not found, whereas get returns nil. There are also several ways to modify a vector (return a new one with the change). The most commonly used one is assoc, which accepts the index at which to associate a new value, along with the value itself:

user> (assoc the-vector 5 60)
[10 20 30 40 50 60]

Vectors have another interesting property. They’re functions that take a single argument. The argument is assumed to be an index, and when the vector is called with a number, the value associated with that index is looked up inside itself. Here’s an example:

user> (the-vector 3)
40

The advantage of this is that vectors can be used where functions are expected. This helps a lot when using functional composition to create higher-level functions. We’ll revisit this aspect of vectors in the next chapter. For now, we’ll examine another useful implementation of the ISeq interface, the map.

Maps

Maps are similar to associative arrays or dictionaries in languages like Python, Ruby, and Perl. A map is a sequence of key-value pairs. The keys can be pretty much any kind of object, and a value can be looked up inside a map with its key. Maps are denoted using braces. Here’s an example of a map using keywords as keys, which, as it turns out, is a common pattern:

user=> (def the-map {:a 1 :b 2 :c 3})
#'user/the-map

Maps can also be constructed using the hash-map function.

user=> (hash-map :a 1 :b 2 :c 3)
{:a 1, :c 3, :b 2}

Here, the-map is a sequence of key-value pairs. The keys are :a, :b, and :c. The values are 1, 2, and 3. Each key-value pair appears in sequence, establishing which value associates with which key. The values can be looked up like this:

user=> (the-map :b)
2

The reason this is valid Clojure code is because a Clojure map is also a function. It accepts a key as its parameter, which is used to look up the associated value inside itself. As a reminder, Clojure keywords (like :a and :b) are also functions. They accept a sequence, such as a map, and look themselves up in the sequence, for example:

user=> (:b the-map)
2

The advantage of both maps and keywords being functions is that it makes function composition more flexible. Both these kinds of objects can be used where functions are needed, resulting in less and clearer code.

Like all Clojure data structures, maps are also immutable. There are several functions that can modify a map, and assoc and dissoc are the ones commonly used.

user=> (def updated-map (assoc the-map :d 4))
#'user/updated-map

user=> updated-map
{:d 4, :a 1, :b 2, :c 3}

user=> (dissoc updated-map :a)
{:b 2, :c 3, :d 4}

Before wrapping up this section, let’s look at some rather convenient functions that can make working with maps easy. First, let’s look at what we want to accomplish. Imagine you had an empty map, and you wanted to store user details in it. With one entry, the map might look like:

(def users {:kyle {
              :date-joined "2009-01-01"
              :summary {
                :average {
                  :monthly 1000
                  :yearly 12000}}}})

Note the use of nested maps. If you wanted to update Kyle’s monthly average, you’d need to write some code, like this:

(defn set-average-in [users-map user type amount]
  (let [user-map (users-map user)
        summary-map (:summary user-map)
        averages-map (:average summary-map)]
    (assoc users-map user
           (assoc user-map :summary
                  (assoc summary-map :average
                         (assoc averages-map type amount))))))

Tedious as it is to write, the set-average-in function could then be used as follows:

user=> (set-average-in users :kyle :monthly 2000)
{:kyle {:date-joined "2009-01-01", :summary {:average {:monthly 2000, :yearly
     12000}}}}

Somewhat equally tedious is this function to read an average value:

(defn average-for [user type]
  (type (:average (:summary (user @users)))))

It works but is rather convoluted. This is where those convenience functions mentioned earlier kick in. The first one is called assoc-in, and here it is in action:

user=> (assoc-in users [:kyle :summary :average :monthly] 3000)
{:kyle {:date-joined "2009-01-01", :summary {:average {:monthly 3000, :yearly
     12000}}}}

This is helpful, because you didn’t have to write a new function to set a new value in the rather deep users map. The general form of assoc-in is

(assoc-in map [key & more-keys] value)

If any nested map doesn’t exist along the way, it gets created and correctly associated. The next convenience function reads values out of such nested maps. This function is called get-in:

user=> (get-in users [:kyle :summary :average :monthly])
1000

(Note that the changes to Kyle’s monthly average aren’t reflected because Clojure’s data structures are immutable. You’ll see how to manage state in chapter 6.)

The final function that’s relevant to this discussion is called update-in, which can be used to update values in such nested maps. To see it in action, imagine you wanted to increase Kyle’s monthly average by 500:

user=> (update-in users [:kyle :summary :average :monthly] + 500)
{:kyle {:date-joined "2009-01-01", :summary {:average {:monthly 1500, :yearly
     12000}}}}

The general form of update-in is

(update-in map [key & more-keys] update-function & args)

This works similarly to assoc-in, in that the keys are used to find what to update with a new value. Instead of supplying the new value itself, you supply a function that accepts the old value (and any other arguments that you can supply as well). The function is applied to these arguments, and the result becomes the new value. The + function here does that job—it takes the old monthly average value of 1000 and adds it to the supplied argument of 500.

A lot of Clojure programs use the map as a core data structure. Often, programmers used to objects in the stateful (data) sense of the word use maps in their place. This is a natural choice and works well.

2.5. Summary

This was a long chapter! We started out installing the Clojure runtime and then addressed the basics of writing code in the language. Specifically, we addressed forms that structure code, such as functions, let, looping, and so on. We also looked at execution control forms, such as if, when, cond, and so on. Armed with this knowledge, you can probably write a fair amount of Clojure code to solve fairly complex problems.

We also visited some of the data types and data structures that come built into the language. Understanding these equips you to use and create the right data abstractions in your programs.

In the next chapter, we’re going to explore more building blocks of Clojure. We’ll begin with a deep dive into functions, in an attempt to understand Clojure’s support for functional programming. We’ll also explore the idea of scope and show how to organize your programs with namespaces. Finally, we’ll explore a concept somewhat unique to Clojure (well, uncommon in imperative languages such as Java and Ruby at least) called destructuring.

The material from the next chapter, combined with this one, should enable you to write almost any program using the core of Clojure. The remainder of this part of the book will focus on the advanced features of the language.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.150.123