Cover by Brian Carper, Christophe Grand, Chas Emerick

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

O'Reilly logo

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:

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

  2. Symbols (such as average or +) evaluate to the named value in the current scope—which can be a function, a named local like numbers in our average 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.

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

Table 1-1. Comparison of call syntax between Clojure, Java, Python, and Ruby

Clojure expressionJava equivalentPython equivalentRuby equivalent

(not k)

!k

not k

not k or ! k

(inc a)

a++, ++a, a += 1, a + 1[a]

a += 1, a + 1

a += 1

(/ (+ x y) 2)

(x + y) / 2

(x + y) / 2

(x + y) / 2

(instance? java.util.List al)

al instanceof java.util.List

isinstance(al, list)

al.is_a? Array

(if (not a) (inc b) (dec b)) [b]

!a ? b + 1 : b - 1

b + 1 if not a else b - 1

!a ? b + 1 : b - 1

(Math/pow 2 10) [c]

Math.pow(2, 10)

pow(2, 10)

2 ** 10

(.someMethod someObj "foo" (.otherMethod otherObj 0))

someObj.someMethod("foo", otherObj.otherMethod(0))

someObj.someMethod("foo", otherObj.otherMethod(0))

someObj.someMethod("foo", otherObj.otherMethod(0))

[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 if and when. Here, the value of the if expression will be either (inc b) or (dec b), depending on the value of (not a).

[c] Here’s your first taste of what it looks like to call Java libraries from Clojure. For details, see Chapter 9.

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.



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

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required