Chapter 1. Down the Rabbit Hole
If you’re reading this book, you are presumably open to learning new programming languages. On the other hand, we assume that you expect reciprocity for the time and effort you’ll expend to learn a new language, some tangible benefits that can make you more productive, your team more effective, and your organization more flexible.
We believe that you will find this virtuous cycle in effect as you learn, apply, and leverage Clojure. As we are fond of saying, Clojure demands that you raise your game, and pays you back for doing so.
As software developers, we often build up a complex and sometimes very personal relationship with our tools and languages. Deciding which raw materials to use is sometimes dominated by pragmatic and legacy concerns. However, all other things being equal, programmers prefer using whatever maximally enhances their productivity and hopefully enables us to fulfill our potential to build useful, elegant systems. As the old saying goes, we want whatever makes the easy stuff easy, and the hard stuff possible.
Why Clojure?
Clojure is a programming language that lives up to that standard. Forged of a unique blend of the best features of a number of different programming languages—including various Lisp implementations, Ruby, Python, Java, Haskell, and others—Clojure provides a set of capabilities suited to address many of the most frustrating problems programmers struggle with today and those we can see barreling toward us over the horizon. And, far from requiring a sea-change to a new or unfamiliar architecture and runtime (typical of many otherwise promising languages over the years), Clojure is hosted on the Java Virtual Machine, a fact that puts to bed many of the most pressing pragmatic and legacy concerns raised when a new language is considered.
To whet your appetite, let’s enumerate some of Clojure’s marquee features and characteristics:
- Clojure is hosted on the JVM
Clojure code can use any Java library, Clojure libraries can in turn be used from Java, and Clojure applications can be packaged just like any Java application and deployed anywhere other Java applications can be deployed: to web application servers; to desktops with Swing, SWT, or command-line interfaces; and so on. This also means that Clojure’s runtime is Java’s runtime, one of the most efficient and operationally reliable in the world.
- Clojure is a Lisp
Unlike Java, Python, Ruby, C++, and other members of the Algol family of programming languages, Clojure is part of the Lisp family. However, forget everything you know (or might have heard rumored) about Lisps: Clojure retains the best of Lisp heritage, but is unburdened by the shortcomings and sometimes anachronistic aspects of many other Lisp implementations. Also, being a Lisp, Clojure has macros, an approach to metaprogramming and syntactic extension that has been the benchmark against which other such systems have been measured for decades.
- Clojure is a functional programming language
Clojure encourages the use of first-class and higher-order functions with values and comes with its own set of efficient immutable data structures. The focus on a strong flavor of functional programming encourages the elimination of common bugs and faults due to the use of unconstrained mutable state and enables Clojure’s solutions for concurrency and parallelization.
- Clojure offers innovative solutions to the challenges inherent in concurrency and parallelization
The realities of multicore, multi-CPU, and distributed computing demand that we use languages and libraries that have been designed with these contexts in mind. Clojure’s reference types enforce a clean separation of state and identity, providing defined concurrency semantics that are to manual locking and threading strategies what garbage collection is to manual memory management.
- Clojure is a dynamic programming language
Clojure is dynamically and strongly typed (and therefore similar to Python and Ruby), yet function calls are compiled down to (fast!) Java method invocations. Clojure is also dynamic in the sense that it deeply supports updating and loading new code at runtime, either locally or remotely. This is particularly useful for enabling interactive development and debugging or even instrumenting and patching remote applications without downtime.
Of course, we don’t expect you to understand all of that, but we do hope the gestalt sounds compelling. If so, press on. By the end of this chapter, you’ll be able to write simple programs in Clojure, and be well on your way to understanding and leveraging it to help realize your potential.
Obtaining Clojure
You’ll need two things to work with the code in this chapter and otherwise explore Clojure on your own:
The Java runtime. You can download the Oracle JVM for free for Windows and Linux (http://java.com/en/download/); it is bundled with or automatically installed by all versions of Mac OS X. Clojure requires Java v1.5 or higher; the latest releases of v1.6 or v1.7 are preferable.
Clojure itself, available from clojure.org (http://clojure.org/downloads). All of the code in this book requires v1.3.0 or higher, and has been tested against v1.4.0 as well.[1] Within the zip file you download, you’ll find a file named something like clojure-1.4.0.jar; this is all you’ll need to get started.
Note
There are a number of different Clojure plug-ins for popular development environments like Eclipse and Emacs; see Tooling for an overview of Clojure tooling. While Clojure’s command-line REPL is sufficient for your first few steps in understanding Clojure, we encourage you to use your favorite text editor or IDE if it has quality Clojure support, or to pick up one that does.
If you don’t yet want to commit to a particular editor or IDE for Clojure development, you should at least use Leiningen, the most popular project management tool for Clojure. It will download Clojure for you, give you a better REPL than Clojure’s default, and you’ll likely be using it on a daily basis for your own projects in short order anyway. See Leiningen for an introduction to it.
If you want to avoid downloading anything right now, you can run many of the samples in this book in the online, in-browser Clojure implementation available at http://tryclj.com.
The Clojure REPL
Many languages have REPLs, often also referred to as
interpreters: Ruby has irb
; Python
has its command-line interpreter; Groovy has its console; even Java has
something akin to a REPL in BeanShell. The “REPL” acronym is derived
from a simple description of what it does:
Read: code is read as text from some input (often
stdin
, but this varies if you’re using a REPL in an IDE or other nonconsole environment).Eval: the code is evaluated, yielding some value.
Print: the value is printed to some output device (often
stdout
, sometimes preceded by other output if the code in question happened to print content itself).Loop: control returns to the read step.
Clojure has a REPL too, but it differs from many other languages’ REPLs in that it is not an interpreter or otherwise using a limited or lightweight subset of Clojure: all code entered into a Clojure REPL is compiled to JVM bytecode as part of its evaluation, with the same result as when code is loaded from a Clojure source file. In these two scenarios, compilation is performed entirely at runtime, and requires no separate “compile” step.[2] In fact, Clojure is never interpreted. This has a couple of implications:
Operations performed in the REPL run at “full speed”; that is to say, there is no runtime penalty or difference in semantics associated with running code in the REPL versus running the same code as part of a “proper” application.
Once you understand how Clojure’s REPL works (in particular, its read and eval phases), you’ll understand how Clojure itself works at the most fundamental level.
With this second point in mind, let’s dig into the Clojure REPL and see if we can find bedrock.
Note
The optimal workflow for programming in Clojure makes much more use of the REPL than is typical in other languages to make the development process as interactive as possible. Taking advantage of this is a significant source of the enhanced productivity—and really, fun!—that Clojure enables. We talk about this extensively in Chapter 10.
% java -cp clojure-1.4.0.jar clojure.main Clojure 1.4.0 user=>
This incantation starts a new JVM process, with a
classpath that includes the clojure.jar file in the current directory,
running the clojure.main
class as its
main entry point.[3] See A classpath primer if you don’t yet
know what the classpath is; for now, you can just think of the classpath
as the JVM’s analogue to Python’s PYTHONPATH
, Ruby’s $:
, and your shell’s PATH
, the set of files and directories from
which the JVM will load classes and resources.
When you see the user=>
prompt, the REPL is ready for you to enter some Clojure code. The
portion of the Clojure REPL prompt preceding =>
is the name of the current
namespace. Namespaces
are like modules or packages; we discuss them extensively later in this
chapter in Namespaces. Clojure REPL sessions
always start in the default user
namespace.
Let’s look at some real code, a function that calculates the average of some numbers in Java, Ruby, and Python:
public static double average (double[] numbers) { double sum = 0; for (int i = 0; i < numbers.length; i++) { sum += numbers[i]; } return sum / numbers.length; } def average (numbers) numbers.inject(:+) / numbers.length end def average (numbers): return sum(numbers) / len(numbers)
Here is the Clojure equivalent:
(defn average [numbers] (/ (apply + numbers) (count numbers)))
defn
defines a new function namedaverage
in the current namespace.The
average
function takes one argument, referred to within its body asnumbers
. Note that there is no type declaration; this function will work equally well when provided with any collection or array of numbers of any type.The body of the
average
function, which sums the provided numbers with(apply + numbers)
,[4] divides that sum by the number of numbers provided—obtained with(count numbers)
—and returns the result of that division operation.
We can enter that defn
expression at the REPL, and then call our function with a vector of
numbers, which yields the expected result:
user=> (defn average [numbers] (/ (apply + numbers) (count numbers))) #'user/average user=> (average [60 80 100 400]) 160
No, Parentheses Actually Won’t Make You Go Blind
Many programmers who don’t already use a Lisp or secretly harbor fond memories of their last usage of Lisp from university blanch at the sight of Lisp syntax. Typical reasons offered for this reaction include:
The particular usage of parentheses to delimit scope, rather than the more familiar braces
{...}
ordo ... end
blocksThe use of prefix notation indicating the operation being performed; e.g.,
(+ 1 2)
rather than the familiar infix1 + 2
These objections are born first out of simple unfamiliarity. The braces that Java (and C and C++ and C# and PHP and…) uses for delimiting scope seem perfectly fine—why bother with what appears to be an ill-conceived animal? Similarly, we’ve all known and used infix notation for mathematics since early childhood—why work to use an unusual notation when what we’ve been using seems to have been so reliable? We are creatures of habit, and outside of building an understanding of why any particular difference may be significant, we understandably prefer the familiar and reliable.
In both cases, the answer is that Clojure did not import its syntactic foundations from other Lisp implementations on a whim; their adoption carries powerful benefits that are worth a minor shift in perspective:
Prefixed operations used uniformly simplify the language’s syntax significantly and eliminate potential ambiguity from nontrivial expressions.
The use of parentheses (as a textual representation of lists) is an outgrowth of Clojure being a homoiconic language. We’ll see what this means in Homoiconicity, but the ramifications of it are manifold: homoiconicity enables the development and use of metaprogramming and domain-specific language constructs simply unavailable in any programming language that is not homoiconic.
After getting through an initial period of unfamiliarity, you will
very likely find that Clojure’s syntax reduces the cognitive load
necessary to read and write code. Quick: is <<
(bit-shift left) in Java executed
before or after &
(bitwise and)
in order of operations? Every time a programmer has to pause and think
about this (or look it up in a manual), every time a programmer has to
go back and add grouping parentheses “just in case,” a mental page fault
has occurred. And, every time a programmer forgets to think about this,
a potential error has entered his code. Imagine a language with no order
of operations to worry about at all; Clojure is that language.
You might be saying, “But there are so many parentheses!” Actually, there aren’t.
In places where it makes sense, Clojure has borrowed a lot of syntax from other languages—like Ruby—for its data literals. Where other Lisps you might have seen use parenthesized lists everywhere, Clojure provides a rich set of literals for data and collections like vectors, maps, sets, and lists, as well as things like records (roughly, Clojure’s corollary to structs).
If you count and compare the number of delimiting characters and
tokens of all kinds (()
, []
, {}
,
Ruby’s ||
and end
, and so on) in Clojure, Java, Ruby, and
Python codebases of similar sizes, you will find that the Clojure code
won’t have appreciably more than the others—and will often have many
fewer thanks to its concision.
Expressions, Operators, Syntax, and Precedence
All Clojure code is made up of expressions, each of which
evaluates to a single value. This is in contrast to many languages that
rely upon valueless statements—such as if
, for
,
and continue
—to control program flow
imperatively. Clojure’s corollaries to these statements are all
expressions that evaluate to a value.
You’ve already seen a few examples of expressions in Clojure:
60
[60 80 100 400]
(average [60 80 100 400])
(+ 1 2)
These expressions all evaluate to a single value. The rules for that evaluation are extraordinarily simple compared to other languages:
Lists (denoted by parentheses) are calls, where the first value in the list is the operator and the rest of the values are parameters. The first element in a list is often referred to as being in function position (as that’s where one provides the function or symbol naming the function to be called). Call expressions evaluate to the value returned by the call.
Symbols (such as
average
or+
) evaluate to the named value in the current scope—which can be a function, a named local likenumbers
in ouraverage
function, a Java class, a macro, or a special form. We’ll learn about macros and special forms in a little bit; for now, just think of them as functions.All other expressions evaluate to the literal values they describe.
Note
Lists in Lisps are often called
s-expressions or
sexprs—short for symbolic
expressions due to the significance of symbols in
identifying the values to be used in calls denoted by such lists.
Generally, valid s-expressions
that can be successfully evaluated are often referred to as
forms: e.g., (if
condition then else)
is an if
form, [60 80 100
400]
is a vector form. Not all s-expressions are forms:
(1 2 3)
is a valid s-expression—a
list of three integers—but evaluating it will produce an error because
the first value in the list is an integer, which is not
callable.
The second and third points are roughly equivalent to most other languages (although Clojure’s literals are more expressive, as we’ll see shortly). However, an examination of how calls work in other languages quickly reveals the complexity of their syntax.
Clojure expression | Java equivalent | Python equivalent | Ruby equivalent |
| | |
|
|
|
| |
| | | |
| | | |
| | | |
| | | |
| | | |
[a] In-place increment and decrement operations have no direct corollary in Clojure, because unfettered mutability isn’t available. See Chapter 2, particularly On the Importance of Values for a complete discussion of why this is a good thing. [b] Remember, even forms that influence control flow in
Clojure evaluate to values just like any other expression,
including |
Notice that call syntax is all over the map (we’re picking on Java here the most, but Python and Ruby aren’t so different):
Infix operators are available (e.g.,
a + 1
,al instanceof List
), but any nontrivial code ends up having to use often-significant numbers of parentheses to override default precedence rules and make evaluation order explicit.Unary operators are seemingly arbitrary in regard to whether they use prefix (e.g.,
!k
and++a
) or postfix position (e.g.,a++
).Static method calls have prefix position, such as
Math.pow(2, 10)
, but…Instance method calls use an unusual variety of infix positions, where the target of the method (which will be assigned to
this
within the body of the method being called) is specified first, with the formal parameters to the method coming after the method name.[5]
In contrast, Clojure call expressions follow one simple rule: the first value in a list is the operator, the remainder are parameters to that operator. There are no call expressions that use infix or postfix position, and there are no difficult-to-remember precedence rules. This simplification helps make Clojure’s syntax very easy to learn and internalize, and helps make Clojure code very easy to read.
Homoiconicity
Clojure code is composed of literal representations of its own data structures and atomic values; this characteristic is formally called homoiconicity, or more casually, code-as-data.[6] This is a significant simplification compared to most other languages, which also happens to enable metaprogramming facilities to a much greater degree than languages that are not homoiconic. To understand why, we’ll need to talk some about languages in general and how their code relates to their internal representations.
Recall that a REPL’s first stage is to read code provided to it by you. Every language has to provide a way to transform that textual representation of code into something that can be compiled and/or evaluated. Most languages do this by parsing that text into an abstract syntax tree (AST). This sounds more complicated than it is: an AST is simply a data structure that represents formally what is manifested concretely in text. For example, Figure 1-1 shows some examples of textual language and possible transformations to their corresponding syntax trees.[7]
These transformations from a textual manifestation of language to an AST are at the heart of how languages are defined, how expressive they are, and how well-suited they are to the purpose of relating to the world within which they are designed to be used. Much of the appeal of domain-specific languages springs from exactly this point: if you have a language that is purpose-built for a given field of use, those that have expertise in that field will find it far easier to define and express what they wish in that language compared to a general-purpose language.
The downside of this approach is that most languages do not provide any way to control their ASTs; the correspondence between their textual syntax and their ASTs is defined solely by the language implementers. This prompts clever programmers to conjure up clever workarounds in order to maximize the expressivity and utility of the textual syntax that they have to work with:
Code generation
Textual macros and preprocessors (used to legendary effect by C and C++ programmers for decades now)
Compiler plug-ins (as in Scala, Project Lombok for Java, Groovy’s AST transformations, and Template Haskell)
That’s a lot of incidental complexity—complexity introduced solely because language designers often view textual syntax as primary, leaving formal models of it to be implementation-specific (when they’re exposed at all).
Clojure (like all Lisps) takes a different path: rather than
defining a syntax that will be transformed into an AST, Clojure programs
are written using Clojure data structures that represent that AST
directly. Consider the requiresRole...
example from Figure 1-1, and see how a Clojure transliteration of the
example is an AST for it (recalling the call
semantics of function position in Clojure lists).
The fact that Clojure programs are represented as data means that Clojure programs can be used to write and transform other Clojure programs, trivially so. This is the basis for macros—Clojure’s metaprogramming facility—a far different beast than the gloriously painful hack that are C-style macros and other textual preprocessors, and the ultimate escape hatch when expressivity or domain-specific notation is paramount. We explore Clojure macros in Chapter 5.
In practical terms, the direct correspondence between code and
data means that the Clojure code you write in the REPL or in a text
source file isn’t text at all: you are programming using Clojure data
structure literals. Recall the simple averaging
function from Example 1-2:
(defn average [numbers] (/ (apply + numbers) (count numbers)))
This isn’t just a bunch of text that is somehow transformed into a
function definition through the operation of a black box; this is a list
data structure that contains four values: the symbol defn
, the symbol average
, a vector data structure containing
the symbol numbers
, and another list
that comprises the function’s body. Evaluating that list data structure
is what defines the function.
The Reader
Although Clojure’s compilation and evaluation machinery operates exclusively on Clojure data structures, the practice of programming has not yet progressed beyond storing code as plain text. Thus, a way is needed to produce those data structures from textual code. This task falls to the Clojure reader.
The operation of the reader is completely defined by a single
function, read
, which reads text
content from a character stream[8] and returns the next data structure encoded in the
stream’s content. This is what the Clojure REPL uses to read text input;
each complete data structure read from that input source is then passed
on to be evaluated by the Clojure runtime.
More convenient for exploration’s sake is read-string
, a function that does the same
thing as read
but uses a string
argument as its content source:
(read-string "42") ;= 42 (read-string "(+ 1 2)") ;= (+ 1 2)
The operation of the reader is fundamentally one of deserialization. Clojure data structures and other literals have a particular textual representation, which the reader deserializes to the corresponding values and data structures.
You may have noticed that values printed by the Clojure REPL have
the same textual representation they do when entered into the REPL:
numbers and other atomic literals are printed as you’d expect, lists are
delimited by parentheses, vectors by square brackets, and so on. This is
because there are duals to the reader’s read
and read-string
functions: pr
and pr-str
, which prints to *out*
[9] and returns as a string the read
able textual representation of Clojure
values, respectively. Thus, Clojure data structures and values are
trivially serialized and deserialized in a way that is both human- and
reader-readable:
(pr-str [1 2 3]) ;= "[1 2 3]" (read-string "[1 2 3]") ;= [1 2 3]
Note
It is common for Clojure applications to use the reader as a
general-purpose serialization mechanism where you might otherwise choose XML or
java.io.Serializable
serialization
or pickling or marshaling, especially in cases where human-readable
serializations are desirable.
Scalar Literals
Scalar literals are reader syntax for noncollection values. Many of these are bread-and-butter types that you already know intimately from Java or very similar analogues in Ruby, Python, and other languages; others are specific to Clojure and carry new semantics.
Strings
Clojure strings are Java Strings (that is, instances of java.lang.String
), and are represented in
exactly the same way, delimited by double quotes:
"hello there" ;= "hello there"
Clojure’s strings are naturally multiline-capable, without any special syntax (as in, for example, Python):
"multiline strings are very handy" ;= "multiline strings\nare very handy"
Booleans
The tokens true
and
false
are used to denote literal
Boolean values in Clojure, just as in Java, Ruby, and Python (modulo
the latter’s capitalization).
nil
nil
in Clojure corresponds to null
in Java, nil
in Ruby, and None
in Python. nil
is also logically false in Clojure
conditionals, as it is in Ruby and Python.
Characters
Character literals are denoted by a backslash:
(class \c) ;= java.lang.Character
Both Unicode and octal representations of characters may be used with corresponding prefixes:
\u00ff ;= \ÿ \o41 ;= \!
Additionally, there are a number of special named character literals for cases where the character in question is commonly used but prints as whitespace:
\space
\newline
\formfeed
\return
\backspace
\tab
Keywords
Keywords evaluate to themselves, and are often used as accessors for the values they name in Clojure collections and types, such as hash maps and records:
(def person {:name "Sandra Cruz" :city "Portland, ME"}) ;= #'user/person (:city person) ;= "Portland, ME"
Here we create a hashmap with two slots, :name
and :city
, and then look up the value of
:city
in that map. This works
because keywords are functions that look themselves up in
collections passed to them.
Syntactically, keywords are always prefixed with a colon, and
can otherwise consist of any nonwhitespace character. A slash
character (/
) denotes a
namespaced keyword, while a keyword prefixed with two colons (::
) is expanded by the reader to a
namespaced keyword in the current namespace—or another namespace if
the keyword started by a namespace alias, ::alias/kw
for example. These have similar
usage and motivation as namespaced entities in XML; that is, being
able to use the same name for values with different semantics or
roles:[10]
(def pizza {:name "Ramunto's" :location "Claremont, NH" ::location "43.3734,-72.3365"}) ;= #'user/pizza pizza ;= {:name "Ramunto's", :location "Claremont, NH", :user/location "43.3734,-72.3365"} (:user/location pizza) ;= "43.3734,-72.3365"
This allows different modules in the same application and disparate groups within the same organization to safely lay claim to particular names, without complex domain modeling or conventions like underscored prefixes for conflicting names.
Keywords are one type of “named” values, so called because
they have an intrinsic name that is accessible using the name
function and an optional namespace
accessible using namespace
:
(name :user/location) ;= "location" (namespace :user/location) ;= "user" (namespace :location) ;= nil
The other named type of value is the symbol.
Symbols
Like keywords, symbols are identifiers, but they evaluate to values in the Clojure runtime they name. These values include those held by vars (which are named storage locations used to hold functions and other values), Java classes, local references, and so on. Thinking back to our original example in Example 1-2:
(average [60 80 100 400]) ;= 160
average
here is a symbol,
referring to the function held in the var named average
.
Symbols must begin with a non-numeric character, and can
contain *
, +
, !
,
-
, _
, and ?
in addition to any alphanumeric
characters. Symbols that contain a slash (/
) denote a namespaced
symbol and will evaluate to the named value in the specified
namespace. The evaluation of symbols to the entity they name depends
upon their context and the namespaces available within that context.
We talk about the semantics of namespaces and symbol evaluation
extensively in Namespaces.
Numbers
Clojure provides a plethora of numeric literals (see Table 1-2). Many of them are pedestrian, but others are rare to find in a general-purpose programming language and can simplify the implementation of certain algorithms—especially in cases where the algorithms are defined in terms of particular numeric representations (octal, binary, rational numbers, and scientific notation).
Warning
While the Java runtime defines a particular range of numeric primitives, and Clojure supports interoperability with those primitives, Clojure has a bias toward longs and doubles at the expense of other widths, including bytes, shorts, ints, and floats. This means that these smaller primitives will be produced as needed from literals or runtime values for interop operations (such as calling Java methods), but pure-Clojure operations will default to using the wider numeric representations.
For the vast majority of programming domains, you don’t need to worry about this. If you are doing work where mathematical precision and other related topics is important, please refer to Chapter 11 for a comprehensive discussion of Clojure’s treatment of operations on primitives and other math topics.
Literal syntax | Numeric type |
| |
|
|
|
|
|
|
| |
[a] |
Any numeric literal can be negated by prefixing it with a dash
(-
).
Let’s take a quick look at the more interesting numeric literals:
- Hexadecimal notation
Just as in most languages, Clojure supports typical hexadecimal notation for integer values;
0xff
is255
,0xd055
is53333
, and so on.- Octal notation
Literals starting with a zero are interpreted as octal numbers. For example, the octal
040
is32
in the usual base-10 notation.- Flexible numeral bases
You can specify the base of an integer in a prefix
BrN
, whereN
is the digits that represent the desired number, andB
is the base or radix by whichN
should be interpreted. So we can use a prefix of2r
for binary integers (2r111
is7
),16r
for hexadecimal (16rff
is 255), and so on. This is supported up to base 36.[11]- Arbitrary-precision numbers
Any numeric literal (except for rational numbers) can be specified as arbitrary-precision by suffixing it appropriately; decimals with an
M
, integers with anN
. Please see Bounded Versus Arbitrary Precision for a full exploration of why and when this is relevant.- Rational numbers
Clojure directly supports rational numbers, also called ratios, as literals in the reader as well as throughout its numeric operators. Rational number literals must always be two integers separated by a slash (
/
).
For a full discussion of rational numbers in Clojure and how they interact with the rest of Clojure’s numerical model, please see Rationals.
Regular expressions
The Clojure reader treats strings prefixed with a hash character as regular expression (regex) literals:
(class #"(p|h)ail") ;= java.util.regex.Pattern
This is exactly equivalent to Ruby’s /.../
regex syntax, with a minor
difference of pattern delimiters. In fact, Ruby and Clojure are
very similar in their handling of regular
expressions:
# Ruby >> "foo bar".match(/(...) (...)/).to_a ["foo bar", "foo", "bar"] ;; Clojure (re-seq #"(...) (...)" "foo bar") ;= (["foo bar" "foo" "bar"])
Clojure’s regex syntax does not require escaping of backslashes as required in Java:
(re-seq #"(\d+)-(\d+)" "1-3") ;; would be "(\\d+)-(\\d+)" in Java ;= (["1-3" "1" "3"])
The instances of java.util.regex.Pattern
that Clojure regex
literals yield are entirely equivalent to those you might create
within Java, and therefore use the generally excellent java.util.regex
regular expression
implementation.[12] Thus, you can use those Pattern
instances directly via Clojure’s
Java interop if you like, though you will likely find Clojure’s
related utility functions (such as re-seq
, re-find
, re-matches
, and others in the clojure.string
namespace) simpler and more
pleasant to use.
Comments
There are two comment types that are defined by the reader:
Single-line comments are indicated by prefixing the comment with a semicolon (
;
); all content following a semicolon is ignored entirely. These are equivalent to//
in Java and JavaScript, and#
in Ruby and Python.Form-level are available using the
#_
reader macro. This cues the reader to elide the next Clojure form following the macro:
(read-string "(+ 1 2 #_(* 2 2) 8)") ;= (+ 1 2 8)
What would have been a list with four numbers—(+ 1 2 4 8)
—yields a list of only three
numbers because the entire multiplication form was ignored due to the
#_
prefix.
Because Clojure code is defined using data structure literals,
this comment form can be far more useful in certain cases than purely
textual comments that affect lines or character offsets (such as the
/* */
multiline comments in Java
and JavaScript). For example, consider the time-tested debugging
technique of printing to stdout
:
(defn some-function […arguments…] …code… (if …debug-conditional… (println …debug-info…) (println …more-debug-info…)) …code…)
Making those println
forms
functionally disappear is as easy as prefixing the
if
form with the #_
reader macro and reloading the function
definition; whether the form spans one or a hundred lines is
irrelevant.
Note
There is only one other way to comment code in Clojure, the
comment
macro:
(when true (comment (println "hello"))) ;= nil
comment
forms can contain any amount of ignored code, but they are
not elided from the reader’s output in the way that #_
impacts the forms following it. Thus,
comment
forms always evaluate to
nil
. This often is not a problem;
but, sometimes it can be inconvenient. Consider a reformulation of
our first #_
example:
(+ 1 2 (comment (* 2 2)) 8) ;= #<NullPointerException java.lang.NullPointerException>
That fails because comment
returns nil
, which is not a valid
argument to +
.
Whitespace and Commas
You may have noticed that there have been no commas between forms, parameters to function calls, elements in data structure literals, and so on:
(defn silly-adder [x y] (+ x y))
This is because whitespace is sufficient to separate values and forms provided to the reader. In addition, commas are considered whitespace by the reader. For example, this is functionally equivalent to the snippet above:
(defn silly-adder [x, y] (+, x, y))
And to be slightly pedantic about it:
(= [1 2 3] [1, 2, 3]) ;= true
Whether you use commas or not is entirely a question of personal style and preference. That said, they are generally used only when doing so enhances the human readability of the code in question. This is most common in cases where pairs of values are listed, but more than one pair appears per line:[13]
(create-user {:name new-username, :email email})
Collection Literals
The reader provides syntax for the most commonplace Clojure data structures:
'(a b :name 12.5) ;; list ['a 'b :name 12.5] ;; vector {:name "Chas" :age 31} ;; map #{1 2 3} ;; set
Since lists are used to denote calls in Clojure, you need to quote
('
) the list literal in order to
prevent the evaluation of the list as a call.
The specifics of these data structures are explored in detail in Chapter 3.
Miscellaneous Reader Sugar
The reader provides for some additional syntax in certain cases to improve concision or regularity with other aspects of Clojure:
Evaluation can be suppressed by prefixing a form with a quote character (
'
); see Suppressing Evaluation: quote.Anonymous function literals can be defined very concisely using the
#()
notation; see Function literals.While symbols evaluate to the values held by vars, vars themselves can be referred to by prefixing a symbol with
#'
; see Referring to Vars: var.Instances of reference types can be dereferenced (yielding the value contained within the reference object) by prefixing
@
to a symbol naming the instance; see Clojure Reference Types.The reader provides three bits of special syntax for macros:
`
,~
, and~@
. Macros are explored in Chapter 5.While there are technically only two Java interop forms, the reader provides some sugar for interop that expands into those two special forms; see Java Interop: . and new.
All of Clojure’s data structures and reference types support metadata—small bits of information that can be associated with a value or reference that do not affect things like equality comparisons. While your applications can use metadata for many purposes, metadata is used in Clojure itself where you might otherwise use keywords in other languages (e.g., to indicate that a function is namespace-private, or to indicate the type of a value or return type of a function). The reader allows you to attach metadata to literal values being read using the
^
notation; see Metadata.
Namespaces
At this point, we should understand much of how the nontrivial parts of the Clojure REPL (and therefore Clojure itself) work:
Read: the Clojure reader reads the textual representation of code, producing the data structures (e.g., lists, vectors, and so on) and atomic values (e.g., symbols, numbers, strings, etc.) indicated in that code.
Evaluate: many of the values emitted by the reader evaluate to themselves (including most data structures and scalars like strings and keywords). We explored earlier in Expressions, Operators, Syntax, and Precedence how lists evaluate to calls to the operator in function position.
The only thing left to understand about evaluation now is how symbols are evaluated. So far, we’ve used them to both name and refer to functions, locals, and so on. Outside of identifying locals, the semantics of symbol evaluation are tied up with namespaces, Clojure’s fundamental unit of code modularity.
All Clojure code is defined and evaluated within a namespace. Namespaces are roughly analogous to modules in Ruby or Python, or packages in Java.[14] Fundamentally, they are dynamic mappings between symbols and either vars or imported Java classes.
One of Clojure’s reference types,[15] vars are mutable storage locations that can hold any value. Within the namespace where they are defined, vars are associated with a symbol that other code can use to look up the var, and therefore the value it holds.
Vars are defined in Clojure using the def
special form, which only ever acts within
the current namespace.[16] Let’s define a var now in the user
namespace, named x
; the name of the var is the symbol that it
is keyed under within the current namespace:
(def x 1) ;= #'user/x
We can access the var’s value using that symbol:
x ;= 1
The symbol x
here is
unqualified, so is resolved within the current
namespace. We can also redefine vars; this is critical for supporting
interactive development at the REPL:
(def x "hello") ;= #'user/x x ;= "hello"
Vars are not variables
Vars should only ever be defined in an interactive
context—such as a REPL—or within a Clojure source file as a way of
defining named functions, other constant values, and the like. In
particular, top-level vars (that is, globally accessible vars mapped
within namespaces, as defined by def
and its variants) should only ever be
defined by top-level expressions, never in the bodies of functions in
the normal course of operation of a Clojure program.
See Vars Are Not Variables for further elaboration.
Symbols may also be namespace-qualified, in which case they are resolved within the specified namespace instead of the current one:
*ns* ;= #<Namespace user> (ns foo) ;= nil *ns* ;= #<Namespace foo> user/x ;= "hello" x ;= #<CompilerException java.lang.RuntimeException: ;= Unable to resolve symbol: x in this context, compiling:(NO_SOURCE_PATH:0)>
Here we created a new namespace using the ns
macro (which has the side effect of
switching us to that new namespace in our REPL), and then referred to
the value of x
in the user
namespace by using the
namespace-qualified symbol user/x
.
Since we only just created this new namespace foo
, it doesn’t have a mapping for the
x
symbol, so attempting to resolve it
fails.
Note
You need to know how to create, define, organize, and manipulate namespaces in order to use Clojure effectively. There is a whole suite of functions for this; please refer to Defining and Using Namespaces for our guidelines in their use.
We mentioned earlier that namespaces also map between symbols and
imported Java classes. All classes in the java.lang
package are imported by default into
each Clojure namespace, and so can be referred to without package
qualification; to refer to un-imported classes, a package-qualified
symbol must be used. Any symbol that names a class evaluates to that
class:
String ;= java.lang.String Integer ;= java.lang.Integer java.util.List ;= java.util.List java.net.Socket ;= java.net.Socket
In addition, namespaces by default alias all of the vars defined in the primary namespace of
Clojure’s standard library, clojure.core
. For example, there is a filter
function defined in clojure.core
, which we can access without
namespace-qualifying our reference to it:
filter ;= #<core$filter clojure.core$filter@7444f787>
These are just the barest basics of how Clojure namespaces work; learn more about them and how they should be used to help you structure your projects in Defining and Using Namespaces.
Symbol Evaluation
With a basic understanding of namespaces under our belt,
we can turn again to the example average
function from Example 1-2 and have a more concrete idea of how it
is evaluated:
(defn average [numbers] (/ (apply + numbers) (count numbers)))
As we learned in Homoiconicity, this is just a canonical textual representation of a Clojure data structure that itself contains other data. Within the body of this function, there are many symbols, each of which refers to either a var in scope in the current namespace or a local value:
/
,apply
,+
, andcount
all evaluate to functions held in vars defined and so named in theclojure.core
namespacenumbers
either defines the sole argument to the function (when provided in the argument vector[numbers]
),[17] or is used to refer to that argument’s value in the body of the function (when used in the(apply + numbers)
and(count numbers)
expressions).
With this information, and recalling the semantics of lists as calls with the operator in function position, you should have a nearly complete understanding of how calls to this function are evaluated:
(average [60 80 100 400]) ;= 160
The symbol average
refers here
to the value of #'average
, the var in
the current namespace that holds the function we defined. That function
is called with a vector of numbers, which is locally bound as numbers
within the body of the average
function. The result of the operations
in that body produce a value—160
—which is then returned to the caller: in
this case, the REPL, which prints it to stdout
.
Special Forms
Ignoring Java interoperability for a moment, symbols in function position can evaluate to only two things:
The value of a named var or local, as we’ve already seen.
A Clojure special form.[18]
Special forms are Clojure’s primitive building blocks of computation, on top of which all the rest of Clojure is built. This foundation shares a lineage with the earliest Lisps, which also defined a limited set of primitives that define the fundamental operations of the runtime, and are taken as sufficient to describe any possible computation.[19] Further, special forms have their own syntax (e.g., many do not take arguments per se) and evaluation semantics.
As you’ve seen, things that are often described as primitive
operations or statements in most languages—including control forms like
when
and operators like addition and
negation—are not primitives in Clojure. Rather, everything that isn’t a
special form is implemented in Clojure itself by bootstrapping from that
limited set of primitive operations.[20] The practical effect of this is that, if Clojure doesn’t
provide a language construct that you want or need, you can likely build
it yourself.[21]
Though all of Clojure is built on top of its special forms, you need to understand what each one does—as you’ll use many of them constantly. Let’s now discuss each one in turn.
Suppressing Evaluation: quote
quote
suppresses evaluation of a Clojure expression. The most
obvious impact of this relates to symbols, which, if they name a var,
evaluate to that var’s value. With quote
, evaluation is suppressed, so symbols
evaluate to themselves (just like strings, numbers, and so on):
(quote x) ;= x (symbol? (quote x)) ;= true
There is reader syntax for quote
; prefixing any form with a quote
character ('
) will expand into a
usage of quote
:
'x ;= x
Any Clojure form can be quoted, including data structures. Doing so returns the data structure in question, with evaluation recursively suppressed for all of its elements:
'(+ x x) ;= (+ x x) (list? '(+ x x)) ;= true
While lists are usually evaluated as calls, quoting a list
suppresses that evaluation, yielding the list itself; in this case, a
list of three symbols: '+
, 'x
, and 'x
. Note that this is exactly what we get if
we “manually” construct the list without using a list literal:
(list '+ 'x 'x) ;= (+ x x)
Tip
You can usually have a peek at what the reader produces by quoting a form. Let’s go meta for a moment and try it first on quote itself:
''x ;= (quote x)
It’s informative to use this trick on other reader sugars:
'@x ;= (clojure.core/deref x) '#(+ % %) ;= (fn* [p1__3162792#] (+ p1__3162792# p1__3162792#)) '`(a b ~c) ;= (seq (concat (list (quote user/a)) ;= (list (quote user/b)) ;= (list c)))
Code Blocks: do
do
evaluates all of the expressions provided to it in order and yields the last expression’s
value as its value. For example:
(do (println "hi") (apply * [4 5 6])) ; hi ;= 120
The values of all but the last expression are discarded, although their side effects do occur (such as printing to standard out as we’re doing here, or manipulations of a stateful object available in the current scope).
Note that many other forms (including fn
, let
,
loop
, and try
—and any derivative of these, such as
defn
) wrap their bodies in an
implicit do
expression, so that multiple inner expressions can be evaluated.
For example, let
expressions—like
this one that defines two locals—provide an implicit do
context to their bodies:
(let [a (inc (rand-int 6)) b (inc (rand-int 6))] (println (format "You rolled a %s and a %s" a b)) (+ a b))
This allows any number of expressions to be evaluated within the
context of the let
form, with only
the final one determining its ultimate result. If let
didn’t wrap its body with a do
form, you would have to add it
explicitly:[22]
(let [a (inc (rand-int 6)) b (inc (rand-int 6))] (do (println (format "You rolled a %s and a %s" a b)) (+ a b)))
Defining Vars: def
We’ve already seen def
in action;[23] it defines (or redefines) a var (with an optional value)
within the current namespace:
(def p "foo") ;= #'user/p p ;= "foo"
Many other forms implicitly create or redefine vars, and
therefore use def
internally.
It is customary for such forms
to be prefixed with “def,” such as defn
, defn-
, defprotocol
, defonce
, defmacro
, and so on.
Warning
Although forms that create or redefine vars have names that
start with “def,” unfortunately not all forms that start with “def”
create or redefine vars. Examples of the latter include deftype
, defrecord
, and defmethod
.
Local Bindings: let
let
allows you to define named references that are lexically
scoped to the extent of the let
expression. Said another way, let
defines locals. For example, this rudimentary static method in
Java:
public static double hypot (double x, double y) { final double x2 = x * x; final double y2 = y * y; return Math.sqrt(x2 + y2); }
is equivalent to this Clojure function:
(defn hypot [x y] (let [x2 (* x x) y2 (* y y)] (Math/sqrt (+ x2 y2))))
The x2
and y2
locals in the respective function/method
bodies serve the same purpose: to establish a named, scoped reference
to an intermediate value.
Note
There are many terms used to talk about named references
established by let
in Clojure
parlance:
locals
local bindings
particular values are said to be let-bound
Bindings and bound
used in connection with let
are
entirely distinct from the binding
macro, which controls scoped
thread-local variables; see Dynamic Scope for more about the
latter.
Note that let
is implicitly
used anywhere locals are required. In particular, fn
(and therefore all other
function-creation and function-definition forms like defn
) uses let
to bind function parameters as locals
within the scope of the function being defined. For example, x
and y
in the hypot
function above are
let-bound by defn
. So, the vector
that defines the set of bindings for a let
scope obeys the same semantics whether
it is used to define function parameters or an auxiliary local binding
scope.
Note
Occasionally, you will want evaluate an expression in the
binding vector provided to let
,
but have no need to refer to its result within the context of the
let
’s body. In these cases, it is
customary to use an underscore as the bound name for such values, so that readers of
the code will know that results of such expressions are going unused
intentionally.
This is only ever relevant when the expression in question is side-effecting; a common example would be printing some intermediate value:
(let [location (get-lat-long) _ (println "Current location:" location) location (find-city-name location)] …display city name for current location in UI…)
Here we’re retrieving our current latitude and longitude using
a hypothetical API, and we’d like to print that out before
converting the location data to a human-recognizable city name. We
might want to rebind the same name a couple of times in the course
of the let
’s binding vector,
paving over those intermediate values. To print out that
intermediate value, we add it to the binding vector prior to
rebinding its name, but we indicate that we are intentionally
ignoring the return value of that expression by naming it _
.
let
has two particular
semantic wrinkles that are very different from locals you may be used
to in other languages:
All locals are immutable. You can override a local binding within a nested
let
form or a later binding of the same name within the same binding vector, but there is no way to bash out a bound name and change its value within the scope of a singlelet
form. This eliminates a source of common errors and bugs without sacrificing capability:The
loop
andrecur
special forms provide for looping cases where values need to change on each cycle of a loop; see Looping: loop and recur.If you really need a “mutable” local binding, Clojure provides a raft of reference types that enforce specific mutation semantics; see Clojure Reference Types.
let
’s binding vector is interpreted at compile time to provide optional destructuring of common collection types. Destructuring can aid substantially in eliminating certain types of verbose (and frankly, dull) code often associated with working with collections provided as arguments to functions.
Destructuring (let, Part 2)
A lot of Clojure programming involves working with various implementations of data structure abstractions, sequential and map collections being two of those key abstractions. Many Clojure functions accept and return seqs and maps generally—rather than specific implementations—and most Clojure libraries and applications are built up relying upon these abstractions instead of particular concrete structures, classes, and so on. This allows functions and libraries to be trivially composed around the data being handled with a minimum of integration, “glue code,” and other incidental complexity.
One challenge when working with abstract collections is being able to concisely access multiple values in those collections. For example, here’s a collection, a Clojure vector:
(def v [42 "foo" 99.2 [5 12]]) ;= #'user/v
Consider a couple of approaches for accessing the values in our sample vector:
(first v) ;= 42 (second v) ;= "foo" (last v) ;= [5 12] (nth v 2) ;= 99.2 (v 2) ;= 99.2 (.get v 2) ;= 99.2
Clojure provides convenience functions for accessing the
first
,second
, andlast
values from a sequential collection.The
nth
function allows you pluck any value from a sequential collection using an index into that collection.All of Clojure’s sequential collections implement the
java.util.List
interface, so you can use that interface’s.get
method to access their contents.
All of these are perfectly fine ways to access a single “top-level” value in a vector, but things start getting more complex if we need to access multiple values to perform some operation:
(+ (first v) (v 2)) ;= 141.2
Or if we need to access values in nested collections:
(+ (first v) (first (last v))) ;= 47
Clojure destructuring provides a concise syntax for declaratively pulling
apart collections and binding values contained therein as named locals
within a let
form. And, because
destructuring is a facility provided by let
, it can be used in any expression that
implicitly uses let
(like fn
, defn
,
loop
, and so on).
There are two flavors of destructuring: one that operates over sequential collections, and another that works with maps.
Sequential destructuring
Sequential destructuring works with any sequential collection, including:
Clojure lists, vectors, and seqs
Any collection that implements
java.util.List
(likeArrayList
s andLinkedList
s)Java arrays
Strings, which are destructured into their characters
Here’s a basic example, where we are destructuring the same
value v
discussed above:
(def v [42 "foo" 99.2 [5 12]]) ;= #'user/v (let [[x y z] v] (+ x z)) ;= 141.2
In its simplest form, the vector provided to let
contains pairs of names and values,
but here we’re providing a vector of symbols—[x y z]
—instead of a scalar symbol name.
What this does is cause the value v
to be destructured sequentially, with
the first value bound to x
within
the body of the let
form, the
second value bound to y
, and the
third value bound to z
. We can
then use those destructured locals like any other locals. This is equivalent to:
(let [x (nth v 0) y (nth v 1) z (nth v 2)] (+ x z)) ;= 141.2
Note
Python has something similar to Clojure’s sequential destructuring, called unpacking. The equivalent to the preceding code snippet in Python would be something like:
>>> v = [42, "foo", 99.2, [5, 12]] >>> x, y, z, a = v >>> x + z 141.19999999999999
The same goes for Ruby:
>> x, y, z, a = [42, "foo", 99.2, [5, 12]] [42, "foo", 99.2, [5, 12]] >> x + z 141.2
Clojure, Python, and Ruby all seem pretty similar on their face; but, as you’ll see as we go along, Clojure goes quite a long ways beyond what Python and Ruby offer.
Destructuring forms are intended to mirror the structure of the collection that is being bound.[24] So, we can line up our destructuring form with the collection being destructured and get a very accurate notion of which values are going to be bound to which names:[25]
[x y z] [42 "foo" 99.2 [5 12]]
Destructuring forms can be composed as well, so we can dig
into the nested vector in v
with
ease:[26]
(let [[x _ _ [y z]] v] (+ x y z)) ;= 59
If we visually line up our destructuring form and the source vector again, the work being done by that form should again be very clear:
[x _ _ [y z ]] [42 "foo" 99.2 [5 12]]
Warning
If our nested vector had a vector inside of it, we could destructure it as well. The destructuring mechanism has no limit to how far it can descend into a deeply nested data structure, but there are limits to good taste. If you’re using destructuring to pull values out of a collection four or more levels down, chances are your destructuring form will be difficult to interpret for the next person to see that code—even if that next person is you!
There are two additional features of sequential destructuring forms you should know about:
- Gathering extra-positional sequential values
You can use
&
to gather values that lay beyond the positions you’ve named in your destructuring form into a sequence; this is similar to the mechanism underlying varargs in Java methods and is the basis ofrest
arguments in Clojure functions:(let [[x & rest] v] rest) ;= ("foo" 99.2 [5 12])
This is particularly useful when processing items from a sequence, either via recursive function calls or in conjunction with a
loop
form. Notice that the value ofrest
here is a sequence, and not a vector, even though we provided a vector to the destructuring form.- Retaining the destructured value
You can establish a local binding for the original collection being destructured by specifying the name it should have via the
:as
option within the destructuring form:(let [[x _ z :as original-vector] v] (conj original-vector (+ x z))) ;= [42 "foo" 99.2 [5 12] 141.2]
Here,
original-vector
is bound to the unchanged value ofv
. This comes in handy when you are destructuring a collection that is the result of a function call, but you need to retain a reference to that unaltered result in addition to having the benefit of destructuring it. Without this feature, doing so would require something like this:(let [some-collection (some-function …) [x y z [a b]] some-collection] …do something with some-collection and its values…)
Map destructuring
Map destructuring is conceptually identical to sequential destructuring—we aim to mirror the structure of the collection being bound. It works with:
Clojure
hash-map
s,array-map
s, and records[27]Any collection that implements
java.util.Map
Any value that is supported by the
get
function can be map-destructured, using indices as keys:Clojure vectors
Strings
Arrays
Let’s start with a Clojure map and a basic destructuring of it:
(def m {:a 5 :b 6 :c [7 8 9] :d {:e 10 :f 11} "foo" 88 42 false}) ;= #'user/m (let [{a :a b :b} m] (+ a b)) ;= 11
Here we’re binding the value for :a
in the map to a
, and the value for :b
in the map to b
. Going back to our visual alignment of
the destructuring form with the (in this case, partial) collection
being destructured, we can again see the structural
correspondence:
{a :a b :b} {:a 5 :b 6}
Note that there is no requirement that the keys used for map lookups in destructuring be keywords; any type of value may be used for lookup:
(let [{f "foo"} m] (+ f 12)) ;= 100 (let [{v 42} m] (if v 1 0)) ;= 0
Indices into vectors, strings, and arrays can be used as keys in a map destructuring form.[28] One place where this can be helpful is if you are representing matrices by using vectors, but only need a couple of values from one. Using map destructuring to pull out two or three values from a 3×3 matrix can be much easier than using a potentially nine-element sequential destructuring form:
(let [{x 3 y 8} [12 0 0 -18 44 6 0 0 1]] (+ x y)) ;= -17
Just as sequential destructuring forms could be composed, so can the map variety:
(let [{{e :e} :d} m] (* 2 e)) ;= 20
The outer map destructuring—{{e :e} :d}
—is
acting upon the top-level source collection m
to pull out the value mapped to :d
. The inner map destructuring—{e :e}
—is acting on
the value mapped to :d
to pull
out its value for :e
.
The coup de grâce is the composition of both map and sequential destructuring, however they are needed to effectively extract the values you need from the collections at hand:
(let [{[x _ y] :c} m] (+ x y)) ;= 16 (def map-in-vector ["James" {:birthday (java.util.Date. 73 1 6)}]) ;= #'user/map-in-vector (let [[name {bd :birthday}] map-in-vector] (str name " was born on " bd)) ;= "James was born on Thu Feb 06 00:00:00 EST 1973"
Map destructuring also has some additional features.
Retaining the destructured value. Just like sequential destructuring, adding an :as
pair to the destructuring form to hold a reference
to the source collection, which you can use like any other
let
-bound value:
(let [{r1 :x r2 :y :as randoms} (zipmap [:x :y :z] (repeatedly (partial rand-int 10)))] (assoc randoms :sum (+ r1 r2))) ;= {:sum 17, :z 3, :y 8, :x 9}
Default values. You can use an :or
pair to provide a defaults map; if a key specified
in the destructuring form is not available in the source
collection, then the defaults map will be consulted:
(let [{k :unknown x :a :or {k 50}} m] (+ k x)) ;= 55
This allows you to avoid either merging the source map into a
defaults map ahead of its destructuring, or manually setting
defaults on destructured bindings that have nil
values in the source collection, which
would get very tiresome beyond one or two
bindings with desired default values:
(let [{k :unknown x :a} m k (or k 50)] (+ k x)) ;= 55
Furthermore, and unlike the code in the above example,
:or
knows the difference between
no value and a false (nil
or
false
) value:
(let [{opt1 :option} {:option false} opt1 (or opt1 true) {opt2 :option :or {opt2 true}} {:option false}] {:opt1 opt1 :opt2 opt2}) ;= {:opt1 true, :opt2 false}
Binding values to their keys’ names. There are often stable names for various values in
maps, and it’s often desirable to bind those values by using the
same names in the scope of the let
form as they are mapped to in the
source map. However, doing this using “vanilla” map destructuring
can get very repetitive:
(def chas {:name "Chas" :age 31 :location "Massachusetts"}) ;= #'user/chas (let [{name :name age :age location :location} chas] (format "%s is %s years old and lives in %s." name age location)) ;= "Chas is 31 years old and lives in Massachusetts."
Having to type the content of each key twice is decidedly
contrary to the spirit of destructuring’s concision. In such cases,
you can use the :keys
, :strs
, and :syms
options to specify keyword, string,
and symbol keys (respectively) into the source map and the names the
corresponding values should be bound to in the let
form without repetition. Our sample
map uses keywords for keys, so we’ll use :keys
for it:
(let [{:keys [name age location]} chas] (format "%s is %s years old and lives in %s." name age location)) ;= "Chas is 31 years old and lives in Massachusetts."
…and switch to using :strs
or :syms
when we know that the
source collection is using strings or symbols for keys:
(def brian {"name" "Brian" "age" 31 "location" "British Columbia"}) ;= #'user/brian (let [{:strs [name age location]} brian] (format "%s is %s years old and lives in %s." name age location)) ;= "Brian is 31 years old and lives in British Columbia." (def christophe {'name "Christophe" 'age 33 'location "Rhône-Alpes"}) ;= #'user/christophe (let [{:syms [name age location]} christophe] (format "%s is %s years old and lives in %s." name age location)) ;= "Christophe is 31 years old and lives in Rhône-Alpes."
You will likely find yourself using :keys
more than :strs
or :syms
; keyword keys are by far the most
common key type in Clojure maps and keyword arguments, and are
the general-purpose accessor by dint of their usage in conjunction
with records.
Destructuring rest sequences as map key/value pairs. We’ve already seen how extra-positional values in sequential destructuring forms can be gathered into a “rest” seq, and map and sequential destructuring can be composed as needed to drill into any given data structure. Here’s a simple case of a vector that contains some positional values, followed by a set of key/value pairs:
(def user-info ["robert8990" 2011 :name "Bob" :city "Boston"]) ;= #'user/user-info
Data like this isn’t uncommon, and handling it is rarely elegant. The “manual” approach in Clojure is tolerable as these things go:
(let [[username account-year & extra-info] user-info {:keys [name city]} (apply hash-map extra-info)] (format "%s is in %s" name city)) ;= "Bob is in Boston"
However, “tolerable” isn’t a very high bar given the
prevalence of sequences of key/value pairs in programming. A better
alternative is a special variety of the compositional behavior
offered by let
’s destructuring
forms: map destructuring of rest seqs. If a rest seq has an even
number of values—semantically, key/value pairs—then it can be
destructured as a map of those key/value pairs instead of
sequentially:
(let [[username account-year & {:keys [name city]}] user-info] (format "%s is in %s" name city)) ;= "Bob is in Boston"
That is a far cleaner notation for doing exactly the same work
as us manually building a hash-map
out of the rest seq and
destructuring that map, and is the basis of Clojure functions’
optional keyword arguments described in “Keyword arguments”.
Creating Functions: fn
Functions are first-class values in Clojure; creating
them falls to the fn
special form,
which also folds in the semantics of let
and do
.
Here is a simple function that adds 10
to the number provided as an
argument:
(fn [x] (+ 10 x))
fn
accepts alet
-style binding vector that defines the names and numbers of arguments accepted by the function; the same optional destructuring forms discussed in Destructuring (let, Part 2) can be applied to each argument here.The forms following the binding vector constitute the body of the function. This body is placed in an implicit
do
form, so each function’s body may contain any number of forms; as withdo
, the last form in the body supplies the result of the function call that is returned to the caller.
The arguments to a function are matched to each name or destructuring form based on their positions in the calling form. So in this call:
((fn [x] (+ 10 x)) 8) ;= 18
8
is the sole argument to the
function, and it is bound to the name x
within the body of the function. This
makes the function call the equivalent of this let
form:
(let [x 8] (+ 10 x))
You can define functions that accept multiple arguments:
((fn [x y z] (+ x y z)) 3 4 12) ;= 19
In this case, the function call is the equivalent of this
let
form:
(let [x 3 y 4 z 12] (+ x y z))
Functions with multiple arities can be created as well; here, we’ll put the function in
a var
so we can call it multiple
times by only referring to the var
’s name:
(def strange-adder (fn adder-self-reference ([x] (adder-self-reference x 1)) ([x y] (+ x y)))) ;= #'user/strange-adder (strange-adder 10) ;= 11 (strange-adder 10 50) ;= 60
When defining a function with multiple arities, each arity’s binding vector and implementation body must be enclosed within a pair of parentheses. Function calls dispatch based on argument count; the proper arity is selected based on the number of arguments that we provide in our call.
In this last example, notice the optional name that we’ve given
to the function, adder-self-reference
. This optional first
argument to fn
can be used within
the function’s bodies to refer to itself—in this case, so that the
single-argument arity can call the two-argument arity with a default
second argument without referring to or requiring any containing
var.
Mutually recursive functions with letfn
Named fn
s (like the
above adder-self-reference
) allow
you to easily create self-recursive functions. What is more tricky
is to create mutually recursive
functions.
For such rare cases, there is the letfn
special form, which allows you to
define several named functions at once, and all these functions will
know each other. Consider these naive reimplementations of odd?
and even?
:
(letfn [(odd? [n] (if (zero? n) false (even? (dec n)))) (even? [n] (or (zero? n) (odd? (dec n))))] (odd? 11)) ;= true
defn builds on fn. We’ve already seen defn
used before, and the example above
should look familiar; defn
is a
macro that encapsulates the functionality of def
and fn
so that you can concisely define
functions that are named and registered in the current namespace
with a given name. For example, these two definitions are
equivalent:
(def strange-adder (fn strange-adder ([x] (strange-adder x 1)) ([x y] (+ x y)))) (defn strange-adder ([x] (strange-adder x 1)) ([x y] (+ x y)))
and single-arity functions can be defined, with the additional parentheses eliminated as well; these two definitions are also equivalent:
(def redundant-adder (fn redundant-adder [x y z] (+ x y z))) (defn redundant-adder [x y z] (+ x y z))
We’ll largely use defn
forms
to illustrate fn
forms for the rest
of this section, simply because calling functions bound to named vars
is easier to read than continually defining the functions to be called
inline.
Destructuring function arguments
defn
supports the destructuring of function arguments
thanks to it reusing let
for
binding function arguments for the scope of a function’s body. You
should refer to the prior comprehensive discussion of destructuring
to remind yourself of the full range of options available; here,
we’ll discuss just a couple of destructuring idioms that are
particularly common in conjunction with functions.
Variadic functions. Functions can optionally gather all additional arguments used in calls to it into a seq; this uses the same mechanism as sequential destructuring does when gathering additional values into a seq. Such functions are called variadic, with the gathered arguments usually called rest arguments or varargs. Here’s a function that accepts one named positional argument, but gathers all additional arguments into a remainder seq:
(defn concat-rest [x & rest] (apply str (butlast rest))) ;= #'user/concat-rest (concat-rest 0 1 2 3 4) ;= "123"
The seq formed for the rest arguments can be destructured just like any other sequence; here we’re destructuring rest arguments to make a function behave as if it had an explicitly defined zero-arg arity:
(defn make-user [& [user-id]] {:user-id (or user-id (str (java.util.UUID/randomUUID)))}) ;= #'user/make-user (make-user) ;= {:user-id "ef165515-6d6f-49d6-bd32-25eeb024d0b4"} (make-user "Bobby") ;= {:user-id "Bobby"}
Keyword arguments. It is often the case that you would like to define a function that can accept many arguments, some of which might be optional and some of which might have defaults. Further, you would often like to avoid forcing a particular argument ordering upon callers.[29]
fn
(and therefore defn
) provides support for such use cases
through keyword arguments, which is an idiom
built on top of the map destructuring of rest
sequences that let
provides. Keyword arguments are pairs of keywords and values
appended to any strictly positional arguments in a function call,
and if the function was defined to accept keyword arguments, those
keyword/value pairs will be gathered into a map and destructured by
the function’s map destructuring form that is placed in the same
position as the rest
arguments seq:
(defn make-user [username & {:keys [email join-date] :or {join-date (java.util.Date.)}}] {:username username :join-date join-date :email email ;; 2.592e9 -> one month in ms :exp-date (java.util.Date. (long (+ 2.592e9 (.getTime join-date))))}) ;= #'user/make-user (make-user "Bobby") ;= {:username "Bobby", :join-date #<Date Mon Jan 09 16:56:16 EST 2012>, ;= :email nil, :exp-date #<Date Wed Feb 08 16:56:16 EST 2012>} (make-user "Bobby" :join-date (java.util.Date. 111 0 1) :email "bobby@example.com") ;= {:username "Bobby", :join-date #<Date Sun Jan 01 00:00:00 EST 2011>, ;= :email "bobby@example.com", :exp-date #<Date Tue Jan 31 00:00:00 EST 2011>}
The
make-user
function strictly requires only one argument, a username. The rest of the arguments are assumed to be keyword/value pairs, gathered into a map, and then destructured using the map destructuring form following&
.In the map destructuring form, we define a default of “now” for the
join-date
value.Calling
make-user
with a single argument returns the user map, populated with defaulted join- and expiration-date values and anil
email value since none was provided in the keyword arguments.Additional arguments provided to
make-user
are interpreted by the keyword destructuring map, without consideration of their order.
Note
Because keyword arguments are built using let
’s map destructuring, there’s nothing
stopping you from destructuring the rest argument map using types
of key values besides keywords (such as strings or numbers or even
collections). For example:
(defn foo [& {k ["m" 9]}] (inc k)) ;= #'user/foo (foo ["m" 9] 19) ;= 20
["m" 9]
is being treated
here as the name of a “keyword” argument.
That said, we’ve never actually seen non-keyword key types used in named function arguments. Keywords are overwhelmingly the most common argument key type used, thus the use of keyword arguments to describe the idiom.
Pre- and postconditions. fn
provides support for
pre- and postconditions for performing assertions with function arguments
and return values. They are valuable features when testing and for
generally enforcing function invariants; we discuss them in Preconditions and Postconditions.
Function literals
We mentioned function literals briefly in Miscellaneous Reader Sugar. Equivalent to blocks in Ruby and lambda
s
in Python, Clojure function literals’ role is
straightforward: when you need to define an anonymous
function—especially a very simple function—they provide the most
concise syntax for doing so.
For example, these anonymous function expressions are equivalent:
(fn [x y] (Math/pow x y)) #(Math/pow %1 %2)
The latter is simply some reader sugar that is expanded into the former; we can clearly see this by checking the result of reading the textual code:[30]
(read-string "#(Math/pow %1 %2)") ;= (fn* [p1__285# p2__286#] (Math/pow p1__285# p2__286#))
The differences between the fn
form and the shorter function literal
are:
No implicit do form. “Regular” fn
forms (and
all of their derivatives) wrap their function bodies in an
implicit do
form, as we discussed in Creating Functions: fn. This allows you to do things
like:
(fn [x y] (println (str x \^ y)) (Math/pow x y))
The equivalent function literal requires an explicit do
form:
#(do (println (str %1 \^ %2)) (Math/pow %1 %2))
Arity and arguments specified using unnamed positional
symbols. The fn
examples above use
the named symbols x
and
y
to specify both the arity
of the function being defined, as well as the names
of the arguments passed to the function at runtime. In contrast,
the literal uses unnamed positional %
symbols, where %1
is the first argument, %2
is the second argument, and so on. In
addition, the highest positional symbol defines the arity of the
function, so if we wanted to define a function that accepted four
arguments, we need only to refer to %4
within the function literal’s
body.
There are two additional wrinkles to defining arguments in function literals:
Function literals that accept a single argument are so common that you can refer to the first argument to the function by just using
%
. So,#(Math/pow % %2)
is equivalent to#(Math/pow %1 %2)
. You should prefer the shorter notation in general.You can define a variadic function[31] and refer to that function’s rest arguments using the
%&
symbol. These functions are therefore equivalent:
(fn [x & rest] (- x (apply + rest))) #(- % (apply + %&))
Function literals cannot be nested. So, while this is perfectly legal:
(fn [x] (fn [y] (+ x y)))
This is not:
#(#(+ % %)) ;= #<IllegalStateException java.lang.IllegalStateException: ;= Nested #()s are not allowed>
Aside from the fact that the bodies of function literals are
intended to be terse, simple expressions, making the prospect of
nested function literals a readability and comprehension nightmare,
there’s simply no way to disambiguate which function’s first
argument %
is referring
to.
Conditionals: if
if
is Clojure’s sole primitive conditional operator. Its
syntax is simple: if the value of the first expression in an if
form is logically
true, then the result of the if
form is the value of the second expression. Otherwise, the result of
the if
form is the value of the
third expression, if provided. The second and third expressions are
only evaluated as necessary.
Clojure conditionals determine logical truth to be anything
other than nil
or false
:
(if "hi" \t) ;= \t (if 42 \t) ;= \t (if nil "unevaluated" \f) ;= \f (if false "unevaluated" \f) ;= \f (if (not true) \t) ;= nil
Note that if a conditional expression is logically false, and no
else
expression is provided, the
result of an if
expression is
nil
.[32]
Many refinements are built on top of if
, including:
when
, best used whennil
should be returned (or no action should be taken) if a condition is false.cond
—similar to theelse if
construction in Java and Ruby, andelif
in Python—allows you to concisely provide multiple conditions to check, along with multiplethen
expressions if a given conditional is true.if-let
andwhen-let
, which are compositions oflet
withif
andwhen
, respectively: if the value of the test expression is logically true, it is bound to a local for the extent of thethen
expression.
Warning
Clojure provides true?
and false?
predicates, but these are unrelated to if
conditionals. For example:
(true? "string") ;= false (if "string" \t \f) ;= \t
true?
and false?
check for the Boolean values
true
and false
, not the logical truth condition
used by if
, which is equivalent
to (and (not (nil? x)) (not (false? x)))
for any
value x
.
Looping: loop and recur
Clojure provides a number of useful imperative looping
constructs, including doseq
and
dotimes
, all of which are built
upon recur
. recur
transfers control to the local-most
loop head without consuming stack space, which is
defined either by loop
or a
function. Let’s take a look at a very simple countdown loop:
(loop [x 5] (if (neg? x) x (recur (dec x)))) ;= -1
loop
establishes bindings via an implicitlet
form, so it takes a vector of binding names and initial values.If the final expression within a
loop
form consists of a value, that is taken as the value of the form itself. Here, whenx
is negative, theloop
form returns the value ofx
.A
recur
form will transfer control to the local-most loop head, in this case theloop
form, resetting the local bindings to the values provided as arguments torecur
. In this case, control jumps to the beginning of theloop
form, withx
bound to the value(dec x)
.
Loop heads are also established by functions, in which case
recur
rebinds the function’s
parameters using the values provided as arguments to recur
:
(defn countdown [x] (if (zero? x) :blastoff! (do (println x) (recur (dec x))))) ;= #'user/countdown (countdown 5) ; 5 ; 4 ; 3 ; 2 ; 1 ;= :blastoff!
Appropriate use of recur. recur
is a very low-level
looping and recursion operation that is usually not
necessary:
When they can do the job, use the higher-level looping and iteration forms found in Clojure’s core library,
doseq
anddotimes
.When “iterating” over a collection or sequence, functional operations like
map
,reduce
,for
, and so on are almost always preferable.
Because recur
does not
consume stack space (thereby avoiding stack overflow errors), recur
is critical when
implementing certain recursive algorithms. In addition, because it
allows you to work with numerics without the overhead of boxed
representations, recur
is very
useful in the implementation of many mathematical and data-oriented
operations. See Visualizing the Mandelbrot Set in Clojure for a live example
of recur
within such
circumstances.
Finally, there are scenarios where the accumulation or
consumption of a collection or set of collections is complicated
enough that orchestrating things with a series of purely functional
operations using map
, reduce
, and so on is either difficult or
inefficient. In these cases, the use of recur
(and sometimes loop
in order to set up intermediate loop
heads) can provide an important escape hatch.
Referring to Vars: var
Symbols that name a var evaluate to that var’s value:
(def x 5) ;= #'user/x x ;= 5
However, there are occasions when you’d like to have a reference
to the var itself, rather than the value it holds. The var
special form does this:
(var x) ;= #'user/x
You’ve seen a number of times now how vars are printed in the
REPL: #'
, followed by a symbol.
This is reader syntax that expands to a call to var
:
#'x ;= #'user/x
You’ll learn a lot more about vars in Vars.
Java Interop: . and new
All Java interoperability—instantiation, static and
instance method invocation, and field access—flows through the
new
and .
special forms. That said, the Clojure
reader provides some syntactic sugar on top of these primitive interop
forms that makes Java interop more concise in general and more
syntactically consistent with Clojure’s notion of function position
for method calls and instantiation. Thus, it’s rare to see .
and new
used directly, but you will nevertheless come across them out in the
wild at some point:
Operation | Java code | Sugared interop form | Equivalent special form usage |
| | | |
| | | |
| | | |
| | | |
| | |
The sugared syntax shown in Table 1-3 is idiomatic and should be preferred
in every case over direct usage of the .
and new
special forms. Java interop is discussed in depth in Chapter 9.
Exception Handling: try and throw
These special forms allow you to participate in and use the exception-handling and -throwing mechanisms in Java from Clojure. They are explained in Exceptions and Error Handling.
Specialized Mutation: set!
While Clojure emphasizes the use of immutable data structures
and values, there are contexts where you need to effect an in-place
mutation of state. The most common settings for this involve the use
of setter and other stateful methods on Java objects you are using in
an interop setting; for the remaining cases, Clojure provides set!
, which can be used to:
Set the thread-local value of vars that have a non-root binding, discussed in Dynamic Scope
Set the value of a Java field, demonstrated in “Accessing object fields”
Set the value of mutable fields defined by
deftype
; see Types for details of that usage
Primitive Locking: monitor-enter and monitor-exit
These are lock primitives that allow Clojure to
synchronize on the monitor associated with every Java object. You
should never need to use these special forms, as there’s a macro,
locking
, that ensures proper
acquisition and release of an object’s monitor. See Locking for details.
Putting It All Together
We’ve continued to pick at the running example from Example 1-2 throughout our first explorations of Clojure:
(defn average [numbers] (/ (apply + numbers) (count numbers)))
We learned how this expression is simply a canonical
representation of Clojure data structures in Homoiconicity. In the beginning, in Expressions, Operators, Syntax, and Precedence, we established that lists are
evaluated as calls, with the value in function position as the operator.
After exploring namespaces, we saw in Symbol Evaluation how the symbols in that data structure
are evaluated at runtime in the course of a call. Now, after we’ve
learned about special forms—in particular, def
and fn
—we have the final pieces in hand to
comprehensively understand what happens when you evaluate this
expression (whether at the REPL or as part of loading a Clojure source
file from disk in a production application).
defn
is simply a shorthand
for:
(def average (fn average [numbers] (/ (apply + numbers) (count numbers))))
So, fn
creates the average
function (recall from Creating Functions: fn that the first argument to fn
here, average
, is a self-reference, so the function
can be called recursively if necessary without looking up the value of
the corresponding var again), and def
registers it as the value of the average
var in the current namespace.
eval
All of the evaluation semantics we’ve been discussing
are encapsulated within eval
, a
function that evaluates a single argument form. We can see very
clearly that, for example, scalars and other literals evaluate to the
values they describe:
(eval :foo) ;= :foo (eval [1 2 3]) ;= [1 2 3] (eval "text") ;= "text"
…and a list will eval
uate to
the return value of the call it describes:
(eval '(average [60 80 100 400])) ;= 160
Warning
While eval
’s semantics
underly all of Clojure, it is itself very rarely used within Clojure
programs. It provides the ultimate in flexibility—allowing you to
evaluate any data that represents a valid Clojure expression—that you simply don’t need
most of the time. In general, if you’re using eval
in application code, it’s likely that
you’re working with far more rope than you need, and might end up
hanging yourself in the process.
Most problems where eval
is
applicable are better solved through judicious application of
macros, which we explore in Chapter 5.
Knowing everything we do now, we can
reimplement the Clojure REPL quite easily.
Remember that read
(or read-string
) is used to produce Clojure
values from their textual representations:
(eval (read-string "(average [60 80 100 400])")) ;= 160
…and we can construct a control loop using a recur
within a function (a loop
form would work as well). Just a
sprinkling of I/O-related functions for printing results and the REPL
prompt, and we have a functioning REPL:
(defn embedded-repl "A naive Clojure REPL implementation. Enter `:quit` to exit." [] (print (str (ns-name *ns*) ">>> ")) (flush) (let [expr (read) value (eval expr)] (when (not= :quit value) (println value) (recur)))) (embedded-repl) ; user>>> (defn average2 ; [numbers] ; (/ (apply + numbers) (count numbers))) ; #'user/average2 ; user>>> (average2 [3 7 5]) ; 5 ; user>>> :quit ;= nil
This REPL implementation is ill-behaved in a variety of ways—for
example, any thrown error leaks out of the loop in embedded-repl
—but it’s a start.[33]
This Is Just the Beginning
What we’ve explored here is the bedrock of Clojure: the fundamental operations of computation (special forms), the interchangeability of code and data, and the tip of the iceberg that is interactive development. On top of this foundation, and in conjunction with the facilities of its JVM host, Clojure provides immutable data structures; concurrency primitives with defined, tractable semantics; macros; and much, much more.
We’ll help you understand much of it throughout the rest of the book, and hopefully tie Clojure into your day-to-day life as a programmer with the practicums in Part IV.
There are some key resources you’ll may want to keep close at hand along the way:
The core API documentation, available at http://clojure.github.com/clojure
The main Clojure mailing list, available at http://groups.google.com/group/clojure, and the
#clojure
IRC channel on Freenode,[34] both friendly places to get quality help with Clojure, no matter your skill or experience levelThe companion site for this book, http://clojurebook.com, which will be maintained over time with additional resources to help you along in learning and using Clojure effectively
[1] Given Clojure’s history with regard to backwards compatibility, the code and concepts in this book should remain applicable to future versions of Clojure as well.
[2] If necessary, you can ahead-of-time compile Clojure to Java class files. See Ahead-of-Time Compilation for details.
[3] Alternatively, you can use java -jar
clojure.jar
, but the -cp
flag and the clojure.main
entry point are both
important to know about; we talk about both in Chapter 8.
[4] Note that +
here is not
a special language operator, as in most other languages. It is a
regular function, no different in type than the one we’re
defining. apply
is also a
function, which applies a function it is provided with to a
collection of arguments (numbers
here); so, (apply + [a b c])
will yield the same
value as (+ a b c)
.
[5] Python uses the same sort of infix position for its
instance methods, but varies from Algol-family brethren by
requiring that methods explicitly name their first parameter,
usually self
.
[6] Clojure is by no means the only homoiconic language, nor is homoiconicity a new concept. Other homoiconic languages include all other Lisps, all sorts of machine language (and therefore arguably Assembly language as well), Postscript, XSLT and XQuery, Prolog, R, Factor, Io, and more.
[7] The natural language parse tree was mostly lifted from http://en.wikipedia.org/wiki/Parse_tree.
[8] Technically, read
requires
a java.io.PushbackReader
as an
implementation detail.
[9] *out*
defaults to stdout
, but can be redirected easily. See
Building a Primitive Logging System with Composable
Higher-Order Functions for an example.
[10] Namespaced keywords are also used prominently with
multimethods and isa?
hierarchies, discussed in depth in Chapter 7.
[11] The implementation limit of java.math.BigInteger
’s radix
support. Note that even though BigInteger
is used for parsing
these literals, the concrete type of the number as
emitted by the reader is consistent with other Clojure
integer literals: either a long
or a big integer if the
number specified requires arbitrary precision to
represent.
[12] See the java.util.regex.Pattern
javadoc for a
full specification of what forms the Java regular expression
implementation supports: http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html.
[13] Questions of style are notoriously difficult to answer in
absolutes, but it would be very rare to see more than two or three
pairs of values on the same line of text in any map literal, set
of keyword arguments, and so on. Further, some forms that expect
pairs of values (such as bindings in let
) are essentially
always delimited by linebreaks rather than
being situated on the same line.
[14] In fact, namespaces correspond precisely with Java packages
when types defined in Clojure are compiled down to Java classes. For
example, a Person
type defined in
the Clojure namespace app.entities
will produce a Java class
named app.entities.Person
. See
more about defining types and records in Clojure in Chapter 6.
[15] See Clojure Reference Types for a full discussion of Clojure’s reference types, all of which contribute different capabilities to its concurrency toolbox.
[16] Remember that the Clojure REPL session always starts in the
default user
namespace.
[17] We’ll get into all the details of how to define functions and therefore their arguments in Creating Functions: fn.
[18] Special forms are always given precedence when resolving
symbols in function position. For example, you can have a var or
local named def
, but you will
not be able to refer to the value of that var or local in
function position—though you can refer to that value anywhere
else.
[19] Paul Graham’s The Roots of Lisp (http://www.paulgraham.com/rootsoflisp.html) is a brief yet approachable precis of the fundamental operations of computation, as originally discovered and enumerated by John McCarthy. Though that characterization of computation was made more than 50 years ago, you can see it thriving in Clojure today.
[20] If you were to open the core.clj file from Clojure’s source
repository, you will see this bootstrapping in action: everything
from when
and or
to defn
and =
is defined in Clojure itself. Indeed, if
you were so motivated, you could implement Clojure (or another
language of your choosing) from scratch, on your own, on top of
Clojure’s special forms.
[21] This sort of syntactic extension generally requires macros, which are treated in detail in Chapter 5.
[22] The other alternative would be for let
(and all other forms that utilize
do
) to (re?) implement its own
semantics of “do several things and return the value of the last
expression”: hardly a reasonable thing to do.
[23] See Namespaces for a discussion of the typical usage of vars as stable references to values in namespaces; see Vars for more a more comprehensive treatment of them, including esoteric usages related to dynamic scope and thread-local references.
[24] Thus the term: destructuring is undoing (de-) the creation of the data structure.
[25] Values in the source collection that have no corresponding
bound name are simply not bound within the context of the
let
form; you do not need to
fully match the structure of the source collection, but
sequential destructuring forms do need to be “anchored” at the
beginning of the source.
[26] Again, note the use of underscores (_
) in this destructuring form to
indicate an ignored binding, similar to the idiom discussed in
the note earlier in this chapter.
[28] This is due to the polymorphic behavior of get
, which looks up values in a
collection given a key into that collection; in the case of
these indexable sequential values, get
uses indices as keys. For more
about get
, see Associative.
[29] Python is a language that supports this usage pervasively, where every argument may be named and provided in any order in a function call, and argument defaults can be provided when a function is defined.
[30] Since the name of the arguments to the function is
irrelevant, the function literal generates a unique symbol for
each argument to refer to them; in this case, p1__285#
and p2__286#
.
[31] See “Variadic functions”.
[32] when
is far more
appropriate for such scenarios.
[33] Clojure’s actual REPL is also implemented in Clojure, in the
clojure.main
namespace, and is
waiting for you if you are interested in seeing how the REPL
you’ll use every day is built.
[34] You can use http://webchat.freenode.net/?channels=#clojure if you aren’t on IRC regularly enough to maintain a desktop client.
Get Clojure Programming now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.