Chapter 3. Building blocks of Clojure

 

This chapter covers

  • Clojure functions in more depth
  • Scope rules in Clojure
  • Clojure namespaces
  • Clojure’s destructuring feature
  • Clojure metadata

 

When people are good at something already (such as a programming language), and they try to learn something new (such as another programming language), they often fall into what Martin Fowler calls an improvement ravine. For programming, the ravine refers to the drop in productivity experienced when one has to relearn how to do things in the new language. I’ve been guilty of switching back to a language I’m already good at in order to get the job done. It sometimes takes me several attempts to get over enough of the ravine to get simple things done. The next few chapters aim to do that—we’ll review the basics of Clojure in more detail. After reading them, you’ll be comfortable enough to solve problems of reasonable complexity. We’ll also cover most constructs of the language, most of which will be familiar to programmers who use other common languages.

First, we’ll examine functions in some detail. Lisp was born in the context of mathematics, and functions are elemental to it. Clojure uses functions as building blocks, and mastering functions is fundamental to learning Clojure. We’ll then look at how namespaces help organize large programs. These are similar to Java packages; they’re a simple way to keep code organized by dividing it into logical modules.

The next section will address vars, which represent the notion of globally visible entities in Clojure. Understanding them is critical to using Clojure effectively. You’ll also learn how they’re special, thanks to their dynamic scoping rules (as opposed to the more familiar lexical scoping rules). This chapter will explore vars and their use in some detail. Next will be a section on destructuring, something that’s rather uncommon in most languages. Destructuring is a neat way of accessing interesting data elements from inside a larger data structure.

Finally, we’ll conclude this chapter with a section on metadata, which is a unique way to associate orthogonal data with ordinary Clojure objects. Without any further ado, let’s review how Clojure creates and uses functions.

3.1. Functions

As discussed in chapter 1, Lisp was born in the context of mathematics and is a functional language. A functional language, among other things, treats functions as first-class elements. This means the following things:

  • Functions can be created dynamically (at runtime).
  • Functions can be accepted as arguments by other functions.
  • Functions can be returned from functions as return values.
  • Functions can be stored as elements inside other data structures (for example, lists).

It’s worth mentioning again that Clojure functions are objects, and because they follow all the rules mentioned, they can be used in some interesting ways. This section will give you an overview of what functions are and how they work, and you’ll see several code examples that illustrate various concepts. We’ll begin by defining simple functions, with both a fixed and a variable number of parameters. We’ll then show how to use the do construct to allow side effects. After that, we’ll examine anonymous functions and a few shortcuts for using them, followed by using recursion as a means of looping. We’ll end the section with a discussion on higher-order functions and closures. To get started, let’s examine the means that Clojure provides to define your own functions.

3.1.1. Defining functions

Functions are defined using the defn macro. The structure of the defn macro is the following:

(defn function-name doc-string? attr-map? [parameter-list]
 conditions-map?
  (expressions))

Before discussing the details of this structure, let’s take a quick look at an example. This example is quite basic, and you’ll see more examples later in this section:

(defn total-cost [item-cost number-of-items]
  (* item-cost number-of-items))

Here, total-cost is the name of the new function object we defined. It accepts two parameters: item-cost and number-of-items. The body of the function is the s-expression that’s the call to the multiply function, which is passed the same two arguments. There’s no explicit return keyword in Clojure; instead, the value of the last expression is automatically returned to the caller of the function.

Notice that defn was described as a macro. There’s a whole chapter on macros coming up (chapter 7), but it’s worth mentioning that the defn form expands to a def. For example, the definition of the total-cost function above is expanded to

(def total-cost (fn [item-cost number-of-items]
  (* item-cost number-of-items)))

total-cost is what Clojure calls a var. You’ll learn about vars later on in this chapter. Note also that the function object is created using the fn special form. Because creating such vars and pointing them to function objects is common, the defn macro was included in the language as a convenience.

If you wanted to, you could add a documentation string to the function, as follows:

(defn total-cost
"return line-item total of the item and quantity provided"
[item-cost number-of-items]
  (* item-cost number-of-items))

In addition to providing a comment that aids in understanding this function, the documentation string can later be called up using the doc macro. If you called the doc macro on this, you’d get the following output:

 (doc total-cost)
-------------------------
user/total-cost
([item-cost number-of-items])
 return line-item total of the item and quantity provided

doc is useful when working with unfamiliar functions. Next, you’ll learn a little more about function parameters. Specifically, you’ll learn how to achieve overloading based on the number of parameters and how to handle a variable number of parameters.

Finally, before moving on, recall the general form of the defn macro from the previous page. It has an optional conditions-map, and you’ll now see what it’s used for. Consider the following function definition:

(defn item-total [price quantity]
  {:pre [(> price 0) (> quantity 0)]
   :post [(> % 0)]}
  (* price quantity))

Here, item-total behaves as a normal function that multiplies its two arguments and returns a result. But at runtime, it runs additional checks as specified by the hash with the two keys :pre and :post. The checks it runs before executing the body of the function are the ones specified with the :pre key (hence called preconditions). In this case, there are two checks: one that ensures that price is greater than zero and a second that ensures that quantity is also greater than zero.

Let’s try it with valid input:

user=> (item-total 20 1)
20

Now let’s try it with invalid input:

user=> (item-total 0 1)
; Evaluation aborted.
Assert failed: (> price 0)
  [Thrown class java.lang.AssertionError]

Note that in this case, the function didn’t compute the result but instead threw an AssertionError error with an explanation of which condition failed. The Clojure runtime automatically takes care of running the checks and throwing an error if they fail.

Now let’s look at the conditions specified by the :post key, called the postconditions. The % in these conditions refers to the return value of the function. The checks are run after the function body is executed, and the behavior in the case of a failure is the same: an AssertionError is thrown along with a message explaining which condition failed.

You’ve seen how to define pre- and postconditions to functions, so now let’s look at a more modular way of approaching this. Consider the following two function definitions:

(defn basic-item-total [price quantity]
  (* price quantity))

(defn with-line-item-conditions [f price quantity]
    {:pre [(> price 0) (> quantity 0)]
     :post [(> % 1)]}
    (apply f price quantity))

basic-line-item is defined without any conditions and is clearly focused on only the business logic of calculating the line-item total. with-line-item-conditions is a higher-order function that accepts a function and the same two arguments. Here it is in action with valid arguments:

user> (with-line-item-conditions basic-item-total 20 1)
20

And here it is with invalid arguments:

user> (with-line-item-conditions basic-item-total 0 1)
; Evaluation aborted.
Assert failed: (> (first args) 0)
  [Thrown class java.lang.AssertionError]

There’s another advantage to this approach apart from keeping basic-item-total clean, and that is with-line-item-conditions can be used with other functions like basic-item-total, allowing these conditions to be reused. Finally, if calling the function as shown previously becomes tedious, you can easily do the following:

(def item-total (partial with-line-item-conditions basic-item-total))

partial is a higher-order function that returns a new function with some arguments fixed. You’ll see it in more detail later in this chapter. The previous usage allows item-total to be used as we did when we defined it in the regular way. Now that you’ve seen how to add pre- and postconditions to your functions, we’re ready to move on. We’ll next look at functions that can accept different sets of parameters.

Multiple arity

The arity of a function is the number of operands it accepts. Clojure functions can be overloaded on arity. In order to define functions with such overloading, you can define the various forms within the same function definition as follows:

(defn function-name
  ([parameter-list-1]
   ;; body
  )
  ([paramter-list-2]
    ;; body
  )
;;more cases
)

Let’s look at an example:

(defn total-cost
  ([item-cost number-of-items]
    (* item-cost number-of-items))
  ([item-cost]
    (total-cost item-cost 1)))

Here, we’ve defined two arities of the total-cost function. The first is of arity 2, and it’s the same as the one we defined earlier. The other is of arity 1, and it accepts only the first parameter, item-cost. Note that you can call any other version of the function from any of the other arities. For instance, in the previous definition, we call the dual-arity version of total-cost from the body of the single-arity one.

Variadic functions

A variadic function is a function of variable arity. Different languages support this in different ways; for example, C++ has the ellipsis, and Java has varargs. In Clojure, the same is achieved with the & symbol:

(defn total-all-numbers [ & numbers]
  (apply + numbers))

Here, total-all-numbers is a function that can be called with any number of arguments. All the arguments are packaged into a single list called numbers, which is available to the body of the function. You can use this form even when you do have a few named parameters. The general form of declaring a variadic function is as follows:

(defn name-of-variadic-function [param-1 param-2 & rest-args]
  (body-of-function))

Here, param-1 and param-2 behave as regular named parameters, and all remaining arguments will be collected into a list called rest-args.

Recursive functions

Recursive functions are those that either directly or indirectly call themselves. Clojure functions can certainly call themselves using their names, but this form of recursion consumes the stack. If enough recursive calls are made, eventually the stack will overflow. This is how things work in most programming languages. There is a feature in Clojure that circumvents this issue. Let’s first write a recursive function that will blow the stack:

(defn count-down [n]
  (if-not (zero? n)
    (do
      (if (= 0 (rem n 100))
        (println "count-down:" n))
      (count-down (dec n)))))

If you try calling count-down with a large number, for instance 100000, you’ll get a StackOverflowError thrown at you. You’ll now see how to ensure that this doesn’t happen.

In the last chapter, you saw the loop/recur construct that allowed you to iterate through sequences of data. The same recur form can be used to write recursive functions. When used in the tail position of a function body, recur binds its arguments to the same names as those specified in its parameter list. Let’s rewrite count-down using recur:

(defn count-downr [n]
  (if-not (zero? n)
    (do
      (if (= 0 (rem n 100))
           (println "count-down:" n))
      (recur (dec n)))))

This now works for any argument, without blowing the stack. The change is minimal because at the end of the function body, recur rebinds the function parameter n to (dec n), which then proceeds down the function body. When n finally becomes zero, the recursion ends. As you can see, writing self-recursive functions is straightforward. Writing mutually recursive functions is a bit more involved, and we’ll look at that next.

Mutually recursive functions

Mutually recursive functions are those that either directly or indirectly call each other. Let’s begin this section by examining an example of such a case. Listing 3.1 shows a contrived example of two functions, cat and hat, that call each other. When given a large enough argument, they’ll throw the same StackOverflowError you saw earlier. Note that the declare macro calls def on each of its arguments. This is useful in cases where a function wants to call another function that isn’t defined yet, as is the case with a pair of mutually recursive functions in the following listing.

Listing 3.1. Mutually recursive functions that can blow the stack
(declare hat)

(defn cat [n]
  (if-not (zero? n)
    (do
      (if (= 0 (rem n 100))
           (println "cat:" n))
      (hat (dec n)))))

(defn hat [n]
  (if-not (zero? n)
    (do
      (if (= 0 (rem n 100))
           (println "hat:" n))
      (cat (dec n)))))

Let’s now fix this problem. We can’t use recur because recur is only useful for self-recursion. Instead, let’s modify the code to use a special Clojure function called trampoline. In order to do so, we’ll make a slight change to the definition of cat and hat. The new functions are shown in the following listing as catt and hatt.

Listing 3.2. Mutually recursive functions that can be called with trampoline
(declare hatt)

(defn catt [n]
  (if-not (zero? n)
    (do
       (if (= 0 (rem n 100))
           (println "catt:" n))
       #(hatt (dec n)))))

(defn hatt [n]
  (if-not (zero? n)
    (do
       (if (= 0 (rem n 100))
           (println "hatt:" n))
       #(catt (dec n)))))

The difference is so minor that you could almost miss it. Consider the definition of catt, where instead of making the recursive call to hatt, you now return an anonymous function that when called makes the call to hatt. The same change is made in the definition of hatt.

Because these functions no longer perform their recursion directly, you have to use a special higher-order function to use them. That’s the job of trampoline, and here’s an example of using it:

(trampoline catt 100000)

This doesn’t blow the stack and works as expected.

You’ve now seen how recursive functions can be written in Clojure. Although using recur and trampoline is the correct and safe way to write such functions, if you’re sure that your code isn’t in danger of consuming the stack, it’s OK to write them without using these. Now that you’ve seen the basics of defining functions, let’s look at a couple of ways to call them.

3.1.2. Calling functions

Because the function object is so fundamental to Clojure, you’ll be calling a lot of functions in your programs. The most common way of doing this looks similar to the following:

(+ 1 2 3 4 5)

Here, the symbol + represents a function that adds its arguments. As a side note, Clo-jure doesn’t have the traditional operators present in other languages. Instead, everything is a first-class function object, defined as any other function. Coming back to the previous example, the + function is variadic, and it adds up all the parameters passed to it and returns 15.

There’s another way to evaluate a function. Let’s say someone handed us a sequence called list-of-expenses, each an amount such as $39.95. In a language such as Java, you’d have to perform some kind of iteration over the list of expense amounts, combined with collecting the result of adding them. In Clojure, you can treat the list of numbers as arguments to a function like +. The evaluation, in this case, is done using a higher-order function called apply:

(apply + list-of-expenses)

The apply function is extremely handy, because it’s quite common to end up with a sequence of things that need to be used as arguments to a function. This is true because a lot of Clojure programs use the core sequence data structures to do their job.

As you saw, apply is a higher-order function that accepts another function as its first parameter. Higher-order functions are those that accept one or more functions as parameters, or return a function, or do both. You’ll now learn a bit more about this powerful concept by looking at a few examples of such functions provided by Clojure.

3.1.3. Higher-Order Functions

As we discussed in the previous chapter, functions in Clojure are first-class entities. Among other things, this means that functions can be treated similar to data: they can be passed around as arguments and can be returned from functions. Functions that do these things are called higher-order functions.

Functional code makes heavy use of higher-order functions. The map function that you saw in the previous chapter is one of the most commonly used higher-order functions. Other common ones are reduce, filter, some, and every. You saw simple examples of map, reduce, and filter in chapter 2. Higher-order functions aren’t just convenient ways of doing things such as processing lists of data but are also the core of a programming technique known as function composition. In this section, we’ll examine a few interesting higher-order functions that are a part of Clojure’s core library.

Every?, some

every? is a function that accepts a predicate function and a sequence. It then calls the predicate on each element of the provided sequence and returns true if they all return true; otherwise, it returns false. Here’s an example:

(def bools [true true true false false])
(every? true? bools)
;; this returns false

some has the same interface as every; that is, it accepts a predicate and a sequence. It then calls the predicate on each element in the sequence and returns the first logically true value it gets. If none of the calls return a logically true value, some returns nil. Here’s an example, which is a quick and dirty way to check if a particular value exists in a sequence:

(some (fn [p] (= "rob" p)) ["kyle" "siva" "rob" "celeste"])
;; returns true
Constantly

constantly accepts a value v and returns a variadic function that always returns the same value v no matter what the arguments.

Complement

complement is a simple function that accepts a function and returns a new one that takes the same number of arguments, does the same thing as the original function does, but returns the logically opposite value.

Partial

partial is a higher-order function that accepts a function f and a few arguments to f but fewer than the number f normally takes. partial then returns a new function that accepts the remaining arguments to f. When this new function is called with the remaining arguments, it calls the original f with all the arguments together. Consider the following function that accepts two parameters, threshold and number, and checks to see if number is greater than threshold:

(defn above-threshold? [threshold number]
  (> number threshold))

To use it to filter a list, you might do this:

(filter (fn [x] (above-threshold? 5 x)) [ 1 2 3 4 5 6 7 8 9])
;; returns (6 7 8 9)

With partial, you could generate a new function and use that instead:

(filter (partial above-threshold? 5) [ 1 2 3 4 5 6 7 8 9])
;; returns (6 7 8 9)
Memoize

Memoization is a technique that prevents functions from computing results for arguments that have already been processed. Instead, return values are looked up from a cache. Clojure provides a convenient memoize function that does this. Consider the following artificially slow function that performs a computation:

(defn slow-calc [n m]
  (Thread/sleep 1000)
  (* n m))

Calling it via a call to time tells you how long it’s taking to run:

(time (slow-calc 5 7))

This prints the following to the console:

(time (slow-calc 5 7))
"Elapsed time: 1000.097 msecs"
35

Now, let’s make this fast, by using the built-in memoize function:

(def fast-calc (memoize slow-calc))

In order for memorize to do its thing, let’s call fast-calc once with a set of arguments (say 5 and 7). You’ll notice that this run appears as slow as before; this time the result has been cached. Now, let’s call it once more via a call to time:

(time (fast-calc 5 7))

Here’s what Clojure prints to the console:

"Elapsed time: 0.035 msecs"
35

This is pretty powerful! Without any work at all, we were able to substantially speed up our function. These are some examples of what higher-order functions can do, and these are only a few of those included with Clojure’s standard library. Next, you’ll learn more about constructing complex functions by building on smaller ones.

Writing higher-order functions

You can create new functions that use existing functions by combining them in various ways to compute the desired result. For example, consider the situation where you need to sort a given list of user accounts, where each account is represented by a hash map, as shown in the following listing.

Listing 3.3. Function composition using higher-order functions
(def users [
  {:username "kyle"
   :balance 175.00
   :member-since "2009-04-16"}

  {:username "zak"
   :balance 12.95
   :member-since "2009-02-01"}
  {:username "rob"
   :balance 98.50
   :member-since "2009-03-30"}
])

(defn username [user]
  (user :username))

(defn balance [user]
  (user :balance))

(defn sorter-using [ordering-fn]
  (fn [users]
    (sort-by ordering-fn users)))

(def poorest-first (sorter-using balance))

(def alphabetically (sorter-using username))

Here, users is a vector of hash maps. Specifically, it contains three users representing Kyle, Zak, and Rob. You define two convenience functions called username and balance, which accept a hash map representing a user and return the appropriate value. With these basics out of the way, let’s get to the meat of what we’re trying to demonstrate here: the idea of sorting a list of users on the two data elements, username and balance.

You define sorter-using as a higher-order function, one that accepts another function called ordering-fn, which will be used as a parameter to sort-by. sort-by itself is a higher-order function provided by Clojure that can sort a sequence based on a user-provided function. Note here that sorter-using returns a function object, defined by the fn special form that you saw earlier. Finally, you define poorest-first and alphabetically as the two desired functions, which sort the incoming list of users by balance and by username.

The two functions username and balance are used in other places, so defining them the way we did is OK. If the only reason they were created was to use them in the definition of poorest-first and alphabetically, then they can be considered clutter. You’ll see a couple of ways to avoid the clutter created by single-use functions, starting with anonymous functions in the next section.

3.1.4. Anonymous functions

As you saw in the previous section, there may be times when you have to create functions for single use. A common example is when another higher-order function needs to be passed a function that might be specific to how it works (for example, our sorter-using function defined earlier). Such single-use functions don’t even need names, because no one else will be calling them. Functions without names are, well, anonymous functions.

You’ve seen anonymous functions before, even if we didn’t call them out. Consider this code snippet from earlier in the chapter:

(def total-cost (fn [item-cost number-of-items]
  (* item-cost number-of-items)))

As we discussed earlier, this code assigns the total-cost var a value, which is the function object created by the fn special form. To be more specific, the function object by itself doesn’t have a name; instead, you use the var with the name total-cost to refer to the function. The function object itself is anonymous. To sum up, anonymous functions can be created using the fn form. Let’s consider a situation where you need a list of dates when your members joined (perhaps for a report). You can use the map function for this:

(map (fn [user] (user :member-since)) users)

Here, you pass the anonymous function that looks up the member-since data element from inside the users hash map into the map function to collect the dates. This is a fairly trivial use case, but there are cases where this will be useful. Before we move on, let’s look at a reader macro that helps with creating anonymous functions.

A shortcut for anonymous functions

We talked about reader macros in chapter 2. One of the reader macros provided by Clojure allows anonymous functions to be defined quickly and easily. The reader macro character that does this is #.

Let’s rewrite the code used to collect a list of member-joining dates using this reader macro:

(map #(% :member-since) users)

That’s much shorter! The #(% :member-since) is equivalent to the anonymous function used in the previous version. Let’s examine this form in more detail.

The #(), with the body of the anonymous function appearing within the parentheses, creates an anonymous function. The % symbol represents a single argument. If the function needs to have more than one argument, then %1, %2, and so on can be used. The body can contain pretty much any code, except for nested anonymous functions defined using another #() reader macro.

We’ll now show another way to write such functions—a way that will result in even shorter code.

3.1.5. Keywords and symbols

You learned about keywords and symbols in chapter 2. They’re some of the most heavily used entities in Clojure code, and they have one more property of interest. They’re also functions, and their use as functions is quite common in idiomatic Clojure.

Keyword functions take either one or two parameters. The first parameter is a hash map, and the keyword looks itself up in the given hash map. For example, consider one of our user hash maps from earlier:

(def person {:username "zak"
             :balance 12.95
             :member-since "2009-02-01"})

To find out what username this corresponds to, you’d do the following:

(person :username)

This would return "zak". But now that you know that keywords behave as functions, you could write the same thing as

(:username person)

This would also return the same "zak". Why would you want to do such a strange-looking thing? To understand this, consider the code we wrote earlier to collect a list of all dates when members joined our service:

(map #(% :member-since) users)

Although this is short and easy to read, you could now make this even clearer by using the keyword as a function:

(map :member-since users)

This is much nicer! Indeed, this is the idiomatic way of working with hash maps and situations such as this one. We said earlier that keyword functions could accept a second optional parameter. This parameter is what gets returned if there is no value in the hash map associated with the keyword. As an example, consider the following two calls:

(:login person)
(:login person :not-found)

The first call returns nil because person doesn’t have a value associated with :login. But if nil was a legitimate value for some key in the hash map, you wouldn’t be able to tell if it returned that or it wasn’t present. To avoid such ambiguity, you’d use the second form shown here, which will return :not-found. This return value tells you clearly that there was nothing associated with the key :login.

Now let’s talk about symbols. In Clojure, symbols are identifiers that represent something else. Examples are users and total-cost, which as you’ve seen represent the list of users and a function. A symbol is the name of something. Every time a particular symbol appears in code, it’s always the same object. For instance, whenever the symbol total appears, it will always refer to the same symbol object. The value it refers to could be different. The Clojure runtime automatically takes care of this, and all programs can rely on this behavior. This is useful when programs manipulate symbolic data.

Normally, when the Clojure runtime sees a symbol like users, it automatically evaluates it and uses the value that the symbol represents. But you may wish to use symbols as is. You may desire to use symbols themselves as values, for instance, as keys in a hash map. To do this, you’d quote the symbols. Everything else works exactly the same as in the case of keywords, including its behavior as a function. Here’s an example:

(def expense {'name "Snow Leopard"
              'cost 29.95})

(expense 'name) ;; returns "Snow Leopard"
('name expense) ;; also returns "Snow Leopard"
('vendor expense) ;; returns nil
('vendor expense :absent) ;;returns :absent

You can see here that symbols behave similar to keywords in this context. The optional parameter that works as the default return value as shown here is a useful technique, which would otherwise require a separate function. Furthermore, it turns out that hash maps and vectors themselves have another interesting property, which is that they’re also functions. Hash maps are functions of their keys, so they return the value associated with the argument passed to them. Consider the example from earlier:

(person :username)

person is a hash map, and this form works because it’s also a function. It returns "zak". Incidentally, hash maps also accept an optional second parameter, which is the default value returned when a value associated with the key is not found, for instance:

(person :username :not-found)

Vectors also behave the same way; they’re functions of their indices. Consider the following example:

(def names ["kyle" "zak" "rob"])
(names 1)

The call to names returns "zak", and this works because the vector names is a function. Note here that vector functions don’t accept a second argument, and if an index that doesn’t exist is specified, an exception is thrown. The fact that vectors and hash maps are functions proves to be useful when function composition is being used to design code. Instead of writing wrapper functions, these data structures can themselves be passed around as functions. This results in cleaner, shorter code.

In this section, we looked at functions in some detail. As we mentioned earlier, functions are a fundamental aspect of Clojure, and you’ll be using them heavily. It’s worth spending some time and experimenting with the various ideas explored in this section, because any nontrivial Clojure program will use most of these concepts. A large part of gaining proficiency with a language like Clojure is understanding and gaining proficiency with functional programming. Functional programming languages are great tools for designing things in a bottom-up way, because small functions can easily be combined into more complex ones. Each little function can be developed and tested incrementally, and this also greatly aids rapid prototyping. Having a lot of small, general functions that then combine to form solutions to the specific problems of the domain is also an important way to achieve flexibility.

Having seen how functions work, you’re ready to tackle another important element in the design of Clojure programs: its scoping rules. In the next section, we’ll explore scope. Scoping rules determine what is visible where, and understanding this is critical to writing and debugging Clojure programs.

3.2. Scope

Now that you’ve seen the basics of defining functions, we’ll take a bit of a detour and show how scope works in Clojure. Scope, as it’s generally known, is the enclosing context where names resolve to associated values. Clojure, broadly, has two kinds of scope: static (or lexical) scope and dynamic scope. Lexical scope is the kind that programming languages such as Java and Ruby offer. A lexically scoped variable is visible only inside the textual block that it’s defined in (justifying the term lexical) and can be determined at compile time (justifying the term static).

Most programming languages only offer lexical scoping, and this is the most familiar kind of scope. The Lisp family has always also offered what are called special variables that follow a different set of rules for scope called dynamic scope. We’ll examine both in this section. We’ll first explore vars and how they can operate as special variables with dynamic scope. Then, we’ll examine lexical scope and how to create new lexically scoped bindings.

3.2.1. Vars and binding

Vars in Clojure are, in some ways, similar to globals in other languages. Vars are defined at the top level, using the def special form. Here’s an example:

(def MAX-CONNECTIONS 10)

After this call, the MAX-CONNECTIONS var is available to other parts of the program. The value of a var is determined by what’s called its binding. In this example, MAX-CONNECTION is bound to the number 10, and such an initial binding is called a root binding. A var can be defined without any initial binding at all, in the following form:

(def RABBITMQ-CONNECTION)

Here, RABBITMQ-CONNECTION is said to be unbound. If another part of the code tries to use its value, an exception will be thrown saying that the var is unbound. To set a value for an unbound var, or to change the value bound to a var, Clojure provides the binding form:

(binding [MAX-CONNECTIONS 20
          RABBITMQ-CONNECTION (new-connection)]
    (
       ;; do something here
     ))

The general structure of the binding form is that it begins with the symbol binding, followed by a vector of even number of expressions. The first of every pair in the vector is a var, and it gets bound to the value of the expression specified by the second element of the pair. Binding forms can be nested, which allows new bindings to be created within each other.

As you saw earlier in this chapter, the defn macro expands to a def form, implying that functions defined using defn are stored in vars. Functions can be redefined using a binding form as well. This is useful for things like implementing aspect-oriented programming or stubbing out behavior for unit tests. You’ll see examples of this in part 2 of this book.

Special variables

There’s one thing to note about vars: they’re not lexically scoped but are dynamically scoped. To understand what this means, let’s again consider the following var:

(def *db-host* "localhost")

If you now call a function like expense-report, which internally uses *db-host* to connect to the database, you’d see numbers retrieved from the local database. For now, let’s use test this with a function that prints the binding to the console:

(defn expense-report [start-date end-date]
  (println *db-host*)) ;; can do real work

Now, once you’ve tested things to your satisfaction, you can have the same code connect to our production database by setting up an appropriate binding:

(binding [*db-host* "production"]
    (expense-report "2010-01-01" "2010-01-07"))

This will run the same code as defined in the expense-report function but will connect to the production database. You can prove that this happens by running the previous code; you’d see "production" printed to the console.

Note here that you managed to change what the expense-report function does, without changing the parameters passed to it (the function connects to a database specified by the binding of the *db-host* var.) This is called action at a distance, and people regard this as something that must be done with caution. The reason is that it can be similar to programming with global variables that can change out from underneath you. Used with caution, it can be a convenient way to alter the behavior of a function.

Such vars that need to be bound appropriately before use are called special variables. A naming convention is used to make this intent clearer: these var names begin and end with an asterisk.

Dynamic scope

You’ve seen how vars in general (and special variables in specific) can be bound to different values. We’ll now explore our earlier statement that vars aren’t governed by lexical scoping rules. We’ll implement a simple form of aspect-oriented programming, specifically a way to add a log statement to functions when they’re called. You’ll see that in Clojure this is a simple thing to do, thanks to dynamic scope.

Scope determines which names are visible at certain points in the code and which names shadow which other ones. Lexical scope rules are simple to understand; you can tell the visibility of all lexically scoped variables by looking at the program text (hence the word lexical). Ruby and Java are lexically scoped.

Dynamic scope doesn’t depend on the lexical structure of code; instead, the value of a var depends on the execution path taken by the program. If a function rebinds a var using a binding form, then the value of the var is changed for all code that executes from that point on, including other functions called. This works in a nested manner, too. If a function were to then use another binding form later on in the call stack, then from that point on, all code would see this second value of the var. When the second binding form completes (execution exits), the previous binding takes over for all code that executes from there onward. Look at the contrived example in the following listing.

Listing 3.4. Listing 3.4 Dynamic scope in action
(def *eval-me* 10)

(defn print-the-var [label]
  (println label *eval-me*))

(print-the-var "A:")

(binding [*eval-me* 20] ;; the first binding
        (print-the-var "B:")
        (binding [*eval-me* 30] ;; the second binding
          (print-the-var "C:"))
        (print-the-var "D:"))

(print-the-var "E:")

Running this code will print the following:

A: 10
B: 20
C: 30
D: 20
E: 10

Let’s walk through the code. First, you create a var called *eval-me* with a root binding of 10. The print-the-var function causes the A: 10 to be printed. The first binding form changes the binding to 20, causes the following B: 20 to be printed. Then the second binding kicks in, causing the C: 30. Now, as the second binding form exits, the previous binding of 20 gets restored, causing the D: 20 to be printed. When the first binding exits after that, the root binding is restored, causing the E: 10 to be printed.

We’ll contrast this behavior with the let form in the next section. In the meantime, let’s implement a kind of aspect-oriented logging functionality for function calls. Consider the following code.

Listing 3.5. A higher-order function for aspect-oriented logging
(defn twice [x]
  (println "original function")
  (* 2 x))

(defn call-twice [y]
  (twice y))

(defn with-log [function-to-call log-statement]
  (fn [& args]
    (println log-statement)
    (apply function-to-call args)))

(call-twice 10)

(binding [twice (with-log twice "Calling the twice function")]
   (call-twice 20))

(call-twice 30)

If you run this, the output will be

original function
20

Calling the twice function
original function
40

original function
60

with-log is a higher-order function that accepts another function and a log statement. It returns a new function, which when called, prints the log statement to the console and then calls the original function with any arguments passed in. Note the action at a distance behavior modification of the twice function. It doesn’t even know that calls to it are now being logged to the console, and, indeed, it doesn’t need to. Any code that uses twice also can stay oblivious to this behavior modification, and as call-twice shows, everything works. Note that when the binding form exits, the original definition of twice is restored. In this way, only certain sections of code (to be more specific, certain call chains) can be modified using the binding form.

We will now examine one more property of bindings.

Thread-local state

As we mentioned in chapter 1, Clojure has language-level semantics for safe concurrency. It supports writing lock-free multithreaded programs. Clojure provides several ways to manage state between concurrently running parts of your programs, and vars is one of them. We’ll say a lot more about concurrency and Clojure’s support for lock-free concurrency in chapter 6. Meanwhile, we’ll look at the dynamic scope property of vars with respect to thread-local storage.

A var’s root binding is visible to all threads, unless a binding form overrides it in a particular thread. If a thread does override the root binding via a call to the binding macro, that binding isn’t visible to any other thread. Again, a thread can create nested bindings, and these bindings exist until the thread exits execution. You’ll see more interaction between binding and threads in the chapter on concurrency.

Laziness and special variables

We mentioned Clojure’s lazy sequences in chapter 1 and talked about how functions like map are lazy. This laziness can be a source of frustration when interplay with dynamic vars isn’t clearly understood. Consider the following code:

(def *factor* 10)

(defn multiply [x]
  (* x *factor*))

This simple function accepts a parameter and multiplies it by the value of *factor*, which is determined by its current binding. Let’s collect a few multiplied numbers using the following:

(map multiply [1 2 3 4 5])

This returns a list containing five elements: (10 20 30 40 50). Now, let’s use a binding call to set *factor* to 20, and repeat the map call:

(binding [*factor* 20]
  (map multiply [1 2 3 4 5]))

Strangely, this also returns (10 20 30 40 50), despite the fact that you clearly set the binding of *factor* to 20. What explains this?

The answer is that a call to map returns a lazy sequence and this sequence isn’t realized until it’s needed. Whenever that happens (in this case. as the REPL tries to print it), the execution no longer occurs inside the binding form, and so *factor* reverts to its root binding of 10. This is why you get the same answer as in the previous case. To solve this, you need to force the realization of the lazy sequence from within the binding form:

(binding [*factor* 20]
  (doall (map multiply [1 2 3 4 5])))

This returns the expected (20 40 60 80 100). This shows the need to be cautious when mixing special variables with lazy forms. doall is a Clojure function that forces realization of lazy sequences, and it’s invaluable in such situations.

In this section, we looked at dynamic scope and the associated binding. Next, we’ll take another look at the let form we saw earlier. Since they look so similar, we’ll also explore the difference between the let and binding forms.

3.2.2. The let form revisited

We briefly explored the let form in chapter 2, where we used it to create local variables. Let’s quickly look at another example of using it:

(let [x 10
      y 20]
  (println "x, y:" x "," y))

Here, x and y are locally bound values. Locals such as these are local because the lexical block of code they’re created in limits their visibility and extent (the time during which they exist). When execution leaves the local block, they’re no longer visible and indeed get garbage collected.

Clojure allows functions to be defined locally, inside a lexically scoped let form. Here’s an example:

(defn upcased-names [names]
  (let [up-case (fn [name]
                  (.toUpperCase name))]
    (map up-case names)))

Here, upcased-names is a function that accepts a list of names and returns a list of the same names, all in uppercase characters. up-case is a locally defined function that accepts a single string and returns an up-cased version of it. The .toUpperCase function (with a prefixed dot) is Clojure’s way of calling the toUpperCase member function on a Java object (in this case a string). You’ll learn about Java interop in chapter 5.

Now, let’s examine the difference between the structurally similar let and binding forms. To do this, let’s first reexamine the behavior of binding via the use of *factor*, as follows:

(def *factor* 10)

(binding [*factor* 20]
  (println *factor*)
  (doall (map multiply [1 2 3 4 5])))

This prints 20 and then returns (20 40 60 80 100), as expected. Now, let’s try the same thing with a let form:

(let [*factor* 20]
  (println *factor*)
  (doall (map multiply [1 2 3 4 5])))

This prints 20 as expected but returns (10 20 30 40 50). This is because although the let changes the binding of *factor* to 20 inside the let itself, it has no effect on the dynamic scope of the *factor* var. Only the binding form can affect the dynamic scope of vars.

Now that you know how the let form works and what it can be used for, let’s look at a useful feature of Clojure that’s possible thanks to two things: lexical scope and the let form.

3.2.3. Lexical closures

Let’s begin our exploration of what a lexical closure is by understanding what a free variable is. A variable is said to be free inside a given form if there’s no binding occurrence of that variable in the lexical scope of that form. Consider the following example:

(defn create-scaler [scale]
  (fn [x]
    (* x scale)))

In this example, within the anonymous function being returned, scale doesn’t appear in any kind of binding occurrence—specifically, it’s neither a function parameter nor created in a let form. Within the anonymous function, therefore, scale is a free variable. Only lexically scoped variables can be free, and the value of the form in which they appear depends on their value. Forms that enclose over free variables (such as the anonymous function shown previously) are called closures. Closures are an extremely powerful feature of languages such as Clojure—in fact, even the name Clojure is a play on the word.

How do you use a closure? Consider the following code:

(def percent-scaler (create-scaler 100))

Here, we’re binding the percent-scaler var to the function object that gets returned by the create-scaler call. As you saw earlier, the parameter scale gets closed over and now lives on inside the percent-scaler closure. You can see this when you make a call to the percent-scaler function:

 (percent-scaler 0.59)
;; returns 59.0

This trivial example shows how closures are easy to create and use. A closure is an important construct in Clojure (it’s no coincidence that the name of the language sounds like the word!). It can be used for information hiding (encapsulation) because nothing from outside the closure can touch the closed-over variables. Because Clojure data structures are immutable (reducing the need to make things private), macros, closures, and multimethods allow for powerful paradigms in which to create programs. It makes traditional object-oriented ideas (a la Java or C++) feel rather confining. You’ll learn about multimethods in chapter 4 and more about macros in chapter 7. We’ll also look at closures a lot more in chapter 13, which takes a deeper look at functional programming concepts.

Now that you understand several basic structural aspects of writing Clojure code, we’ll address an organizational construct of Clojure: the namespace. Understanding how to use namespaces will aid you in writing larger programs that need to be broken into pieces for the sake of modularity and manageability.

3.3. Namespaces

When a program becomes larger than a few functions, computer languages allow the programmer to break it up into parts. An example of this facility is the package system in Java. Clojure provides the concept of namespaces for the same purpose. Programs can be broken up into parts, each being a logical collection of code—functions, vars, and the like.

Another reason why namespaces are useful is to avoid name collisions in different parts of programs. Imagine that you were writing a program that dealt with students, tests, and scores. If you were to then use an external unit-testing library that also used the word test, Clojure might complain about redefinition! Such problems can be handled by writing code in its own namespace.

3.3.1. ns

The *ns* var is bound to the currently active namespace. The ns macro sets the current namespace to whatever is specified. Here’s the general form of the ns macro:

(ns name & references)

The name, as mentioned previously, is the name of the namespace being made current. If it doesn’t already exist, it gets created. The references that follows the name are optional and can be one or more of the following: use, require, import, load, or gen-class. You’ll see some of these in action in this section and in chapter 5, which covers Java interop. First, let’s look at an example of defining a namespace:

(ns org.currylogic.damages.calculators)

(defn highest-expense-during [start-date end-date]
 ;; (logic to find the answer)
)

highest-expense-during is now a function that lives in the namespace with the name of org.currylogic.damages.calculators. In order to use it, code outside this namespace would need to make a call (directly or indirectly) to use, require, or import (if the library is compiled into a JAR). We’ll explore these now through an example.

 

Public versus private functions

Before moving on to the next section, let’s take a quick look at private functions versus public functions. In Clojure, all functions belong to a namespace. The defn macro creates public functions, and these can be called from any namespace. In order to create private functions, Clojure provides the defn- macro, which works exactly the same, but such functions can only be called from within the namespace they’re defined in.

 

Use, require

Let’s imagine that we we’re writing an HTTP service that responds to queries about a user’s expenses. Let’s further imagine that we’re going to deal with both XML and JSON. In order to handle XML, we can use the Clojure-provided XML functions that live in the clojure.xml namespace.

As far as handling JSON is concerned, ideally we wouldn’t have to write code to handle the format. It turns out that Dan Larkin has written an excellent library for this purpose; it’s called clojure-json and lives on github.com. Clojure enjoys incredible support as far as libraries are concerned. Often, either a pure Clojure library exists for the purpose under question or a suitable Java library can be wrapped with Clojure.

Once you’ve downloaded the libraries, put the JAR files somewhere convenient (usually inside the lib folder), and ensure that they’re on the JVM’s classpath. The following listing shows what the code looks like now that you’ve picked the two libraries you need.

Listing 3.6. Using external libraries by calling use
(ns org.currylogic.damages.http.expenses)

(use 'org.danlarkin.json)
(use 'clojure.xml)
(defn import-transactions-xml-from-bank [url]
  (let [xml-document (parse url)]
    ;; more code here

(defn totals-by-day [start-date end-date]
  (let [expenses-by-day (load-totals start-date end-date)]
    (encode-to-str expenses-by-day)))

Here, parse and encode-to-str are functions that come from the clojure.xml and clojure-json libraries. The reason they’re available is that we called use on their namespaces. use takes all public functions from the namespace and includes them in the current namespace. The result is as though those functions were written in the current namespace. Although this is easy, and sometimes desirable, it often makes the code a little less understandable in terms of seeing where such functions are defined. require solves this problem, as shown in here.

Listing 3.7. Using external libraries by calling require
(ns org.currylogic.damages.http.expenses)

(require '(org.danlarkin [json :as json-lib]))
(require '(clojure [xml :as xml-core]))

(defn import-transactions-xml-from-bank [url]
  (let [xml-document (xml-core/parse url)]
    ;; more code here

(defn totals-by-day [start-date end-date]
  (let [expenses-by-day (load-totals start-date end-date)]
    (json-lib/encode-to-str expenses-by-day)))

require makes functions available to the current namespace, as use does, but doesn’t include them the same way. They must be referred to using the full namespace name or the aliased namespace using the as clause, as shown in listing 3.7.

Reload and reload-all

As described in chapter 1, typical programming workflow in Clojure involves building up functions in an incremental fashion. As functions are written or edited, the namespaces that they belong to need to be reloaded in the REPL so they can be tested. You can do this by using the following:

(use 'org.currylogic.damages.http.expenses :reload)

(require '(org.currylogic.damages.http [expenses :as exp]) :reload)

:reload can be replaced with :reload-all to reload all libraries that are used either directly or indirectly by the specified library.

Before wrapping up this section on namespaces, we’ll explore some options that Clojure provides to work with them programmatically.

3.3.2. Working with namespaces

Apart from the convenience offered by namespaces in helping keep code modular (and guarding from name collisions), Clojure namespaces can be accessed program-matically. In this section, we’ll review a few useful functions to do this.

Create-ns, in-ns

create-ns is a function that accepts a symbol and creates a namespace named by it if it doesn’t already exist. in-ns is a function that accepts a single symbol as argument and switches the current namespace to the one named by it. If it doesn’t exist, it’s created.

All-ns, find-ns

The no-argument function all-ns returns a list of all namespaces currently loaded. The find-ns function accepts a single symbol as argument and checks to see if it names a namespace. If so, it returns true, else nil.

Ns-interns, ns-publics

ns-interns is a function that accepts a single argument, a symbol that names a namespace, and returns a map containing symbols to var mappings from the specified namespace. ns-publics is similar to ns-interns but instead of returning a map that contains information about all vars in the namespace, it returns only the public ones.

Ns-resolve, resolve

ns-resolve is a function that accepts two arguments: a symbol naming a namespace and another symbol. If the second argument can be resolved to either a var or a Java class in the specified namespace, the var or class is returned. If it can’t be resolved, the function returns nil. resolve is a convenience function that accepts a single symbol as its argument and tries to resolve it (such as ns-resolve) in the current namespace.

Ns-unmap, remove-ns

ns-unmap accepts a symbol naming a namespace and another symbol. The mapping for the specified symbol is removed from the specified namespace. remove-ns accepts a symbol naming a namespace and removes it entirely. This doesn’t work for the clojure.core namespace.

These are some of the functions provided by Clojure to programmatically work with namespaces. They’re useful in controlling the environment in which certain code executes. An example of this will appear in the last chapter, on domain-specific languages.

So far, you’ve seen a lot of the basics of Clojure and should now be in a position to read and write programs of reasonable complexity. In the next section, you’ll see a feature that isn’t found in languages such as Java and Ruby, namely, destructuring.

3.4. Destructuring

Several programming languages provide a feature called pattern matching, which is a form of function overloading based on structural patterns of arguments (as opposed to their number or types). Clojure has a somewhat less-general form of pattern matching called destructuring. In Clojure, destructuring lets programmers bind names to only those parts of sequences that they care about. To see how this works, look at the following code, which doesn’t use destructuring:

(defn describe-salary [person]
  (let [first (:first-name person)
        last (:last-name person)
        annual (:salary person)]
    (println first last "earns" annual)))

Here, the let form doesn’t do much useful work—it sets up local names for parts of the incoming person sequence. By using Clojure’s destructuring capabilities, such code clutter can be eliminated:

(defn describe-salary-2 [{first :first-name
                       last :last-name
                       annual :salary}]
 (println first last "earns" annual))

Here, the incoming sequence (in this case a hash map) is destructured, and useful parts of it are bound to names within the function’s parameter-binding form. In fact, extracting values of certain keys from inside hash maps is so common that Clojure provides an even more convenient way of doing this. You’ll see that and more ways to destructure hash maps in section 3.4.2, but before that let’s examine destructuring vectors.

3.4.1. Vector bindings

Vector destructuring supports any data structure that implements the nth function, including vectors, lists, seqs, arrays, and strings. This form of destructuring consists of a vector of names, each of which is assigned to the respective elements of the expression, looked up via the nth function. An example will make this clear:

(defn print-amounts [[amount-1 amount-2]]
  (println "amounts are:" amount-1 "and" amount-2))

(print-amounts [10.95 31.45])

;;this prints the following –
amounts are: 10.95 and 31.45

This implementation of print-amounts is short and clear: you can read the parameter list and see that the single argument will be broken into two parts named amount-1 and amount-2. The alternative is to use a let form inside the function body to set up amount-1 and amount-2 by binding them to the first and last values of the incoming vector.

There are several options when it comes to using vector bindings. Let’s imagine that the function print-amounts takes a vector that could contain two or more amounts (instead of only two in our contrived example). Here’s how you could deal with that situation:

Using & and :as

Consider the following example of destructuring:

(defn print-amounts-multiple [[amount-1 amount-2 & remaining]]
  (println "Amounts are:" amount-1 "," amount-2 "and" remaining))

If you make the following call

(print-amounts-multiple [10.95 31.45 22.36 2.95])

Clojure would print the following:

Amounts are: 10.95 , 31.45 and (22.36 2.95)

As shown here, the name following the & symbol gets bound to a sequence containing all the remaining elements from the sequence being destructured.

Another useful option is the :as keyword. Here’s another rather contrived example:

(defn print-all-amounts [[amount-1 amount-2 & remaining :as all]]
  (println "Amounts are:" amount-1 "," amount-2 "and" remaining)
  (println "Also, all the amounts are:" all))

When we call this function as follows

(print-all-amounts [10.95 31.45 22.36 2.95])

it results in the following being printed to the console:

Amounts are: 10.95 , 31.45 and (22.36 2.95)
Also, all the amounts are: [10.95 31.45 22.36 2.95]

Destructuring vectors makes it easy to deal with the data inside them. What’s more, Clojure allows nesting of vectors in destructuring bindings.

Nested vectors

Suppose you had a vector of vectors. Each inner vector was a pair of data—the first being a category of expense and the second the amount. If you wanted to print the category of the first expense amount, you could do the following:

(defn print-first-category [[[category amount] & _ ]]
  (println "First category  was:" category)
  (println "First amount was:" amount))

Running this with an example

(def expenses [[:books 49.95] [:coffee 4.95] [:caltrain 2.25]])
(print-first-category expenses)

results in Clojure printing the following:

First category  was: :books
First amount was: 49.95

You’ve seen how vector destructuring is done, and it’s quite powerful. Note that in the argument list of print-first-category, we used & _ to ignore the remaining elements of the vector that we didn’t care about. One thing to remember is that destructuring can take place in any binding form including function parameter lists and let forms. Another thing to remember is that vector destructuring works for any data type that supports the nth and nthnext functions. On a practical level, for instance, if you were to implement the ISeq interface and create your own sequence data type, you’d be able to natively use not only all of Clojure’s core functions but also such destructuring.

Before closing out this section on destructuring, let’s look at another useful form of destructuring binds—the one that uses hash maps.

3.4.2. Map bindings

You saw how convenient it is to destructure vectors into relevant pieces and bind only those instead of the whole vector. Clojure supports similar destructuring of maps. To be specific, Clojure supports destructuring of any associative data structure, which includes maps, strings, vectors, and arrays. Maps, as you know, can have any key, whereas strings, vectors, and arrays have integer keys. The destructuring binding form looks similar to ones you saw earlier; it’s a map of key-expression pairs, where each key name is bound to the value of the respective initialization expression.

Let’s look at an the example from earlier:

(defn describe-salary-2 [{first :first-name
                          last :last-name
                          annual :salary}]
   (println first last "earns" annual))

As we noted earlier, first, last, and annual get bound to the respective values from the hash map passed to describe-salary-2. Let’s now suppose that you also want to bind a bonus percentage, which may or may not exist. Clojure provides a convenient option in map destructuring bindings to handle such optional values, using the :or keyword:

(defn describe-salary-3 [{first :first-name
                          last :last-name
                          annual :salary
                          bonus :bonus-percentage
                          :or {bonus 5}}]
  (println first last "earns" annual "with a" bonus "percent bonus"))

When called with arguments that do contain all keys that are being destructured, it works similar to the previous case:

(def a-user {:first-name "pascal"
             :last-name "dylan"
             :salary 85000
             :bonus-percentage 20})

(describe-salary-3 a-user)

This prints the following to the console:

pascal dylan earns 85000 with a 20 percent bonus

Here’s how it works if you call the function with an argument that doesn’t contain a bonus:

(def another-user {:first-name "basic"
                   :last-name "groovy"
                   :salary 70000})

(describe-salary-3 another-user)

This binds bonus to the default value specified via the :or option. The output is

basic groovy earns 70000 with a 5 percent bonus

Finally, similar to the case of vectors, map bindings can use the :as option to bind the complete hash map to a name. Here’s an example:

(defn describe-person [{first :first-name
                        last :last-name
                        bonus :bonus-percentage
                        :or {bonus 5}
                        :as p}]
  (println "Info about" first last "is:" p)
  (println "Bonus is:" bonus "percent"))

An example of using this function is

(def third-user {:first-name "lambda"
                :last-name "curry"
                :salary 95000})
(describe-person third-user)

This causes the following to be echoed on the console:

Info about lambda curry is: {:first-name lambda,
                             :last-name curry,
                             :salary 95000}
Bonus is: 5 percent

This is all convenient and results in short, readable code. Clojure provides a couple of options that make it even more easy to destructure maps: the :keys, :strs, and :syms keywords. Let’s look the how to use :keys by writing a small function to greet our users:

(defn greet-user [{:keys [first-name last-name]}]
   (println "Welcome," first-name last-name))

When you run this, first-name and last-name get bound to values of :first-name and :last-name from inside the argument map. Let’s try it:

(def roger {:first-name "roger" :last-name "mann" :salary 65000})
(greet-user roger)

The output might look like this:

Welcome, roger mann

If your keys were strings or symbols (instead of keywords as in these examples), you’d use :strs or :syms.

We covered various ways that Clojure supports destructuring large, complex data structures into their components. This is a useful feature because it results in code that’s shorter and clearer. It improves the self-documentation nature of well-written code, because the destructuring binding tells the reader exactly what parts of the incoming data structure are going to be used in the code that follows.

Our final stop in this chapter will be to explore another neat feature of Clojure—that of metadata.

3.5. Metadata

Metadata means data about data. Clojure supports tagging data (for example, objects like maps and lists) with other data, which can be completely unrelated to the tagged data. For instance, you might want to tag some data that was read over an insecure network.

For example, let’s use the tags :safe and :io to determine if something is considered a security threat and if it came from an external I/O source. Here’s how you might use metadata to represent such information:

(def untrusted (with-meta {:command "clean-table" :subject "users"}
                          {:safe false :io true}))

Objects with metadata can be used like any other objects. The additional metadata doesn’t affect their values. In fact, if you were to check what untrusted was at the REPL, the metadata won’t even appear:

user=> untrusted
{:command "clean-table", :subject "users"}

If you do want to examine the metadata associated with the object, you can use the meta function:

user=> (meta untrusted)
{:safe false, :io true}

As mentioned earlier, the metadata isn’t part of the logical value of the data, so it also doesn’t affect equality. Further, when new objects are created from those that have metadata, the metadata carries over, for example:

user=> (def still-suspect (assoc untrusted :complete? false))
#'user/still-suspect

user=> (meta still-suspect)
{:safe false, :io true}

Functions and macros can also be defined with metadata. Here’s an example:

(defn
  testing-meta "testing metadata for functions"
  {:safe true :console true}
  []
   (println "Hello from meta!"))

Let’s try using the meta function to check that the metadata was set correctly:

user=> (meta testing-meta)
nil

This returns nil because the metadata is associated with the var testing-meta not the function object itself. To access the metadata, you’d have to pass the testing-meta var to the meta function. You can do this as follows:

user=> (meta (var testing-meta))
{:ns #<Namespace user>,
 :name testing-meta,
:file "NO_SOURCE_FILE",
:line 1, :arglists ([]),
:console true,
:safe true,
:doc "testing metadata for functions"}

Metadata is useful in many situations where you want to tag things for purposes orthogonal to the data they represent. Annotations are one example where you might perform certain tasks if objects are annotated a certain way, such as if their metadata contains a certain key and value. Clojure internally uses metadata quite a lot; for example, the :doc key is used to hold the documentation string for functions and macros, the :macro key is set to true for functions that are macros, the :file key is used to keep track of what source file something was defined in, and so on.

3.6. Summary

This was another long chapter! We explored a few important aspects of writing code in Clojure. We looked at functions, which form the basis of the language, and we saw several ways to create and compose functions. We also examined scope—both lexical and dynamic—and how it works in Clojure. We also showed that once programs start getting large, namespaces can be used to break up and organize them. Finally, we looked at the destructuring capabilities of Clojure—a feature that comes in handy when writing functions or let forms.

Between the previous chapter and this one, we covered most of the basic features of the language. You can write fairly non-trivial programs with what you’ve learned so far. The next few chapters focus on a few features that are unique to Clojure—things like Java interoperability, concurrency support, multimethods, the macro system, and more.

In the next chapter, you’ll learn about multimethods. You’ll see how inheritance-based polymorphism is an extremely limited way of achieving polymorphic behavior and how multimethods are an open-ended system to create your own version of polymorphism that could be specific to your problem domain. By combining ideas from the next chapter with those we explored in this one, such as higher-order functions, lexical closures, and destructuring, you can create some rather powerful abstractions.

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

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