In Java, expressions return values, whereas statements do not.
// "if" is a statement because it doesn't return a value:
String s;
if (x > 10) {
s = "greater";
} else {
s = "less or equal";
}
obj.someMethod(s);
// Ternary operator is an expression; it returns a value:
obj.someMethod(x > 10 ? "greater" : "less or equal");In Clojure, however, everything is an expression! Everything returns a value, and a block of multiple expressions returns the last value. Expressions that exclusively perform side-effects return nil.
Accordingly, flow control operators are expressions, too!
Flow control operators are composable, so we can use them anywhere. This leads to less duplicate code, as well as fewer intermediate variables.
Flow control operators are also extensible via macros, which allow the compiler to be extended by user code. We won’t be discussing macros today, but you can read more about them at Macros, Clojure from the Ground Up, or Clojure for the Brave and True, among many other places.
if is the most important conditional expression - it consists of a condition, a "then", and an "else". if will only evaluate the branch selected by the conditional.
user=> (str "2 is " (if (even? 2) "even" "odd"))
2 is even
user=> (if (true? false) "impossible!") ;; else is optional
nilIn Clojure, all values are logically true or false. The only "false" values are false and nil - all other values are logically true.
user=> (if true :truthy :falsey)
:truthy
user=> (if (Object.) :truthy :falsey) ; objects are true
:truthy
user=> (if [] :truthy :falsey) ; empty collections are true
:truthy
user=> (if 0 :truthy :falsey) ; zero is true
:truthy
user=> (if false :truthy :falsey)
:falsey
user=> (if nil :truthy :falsey)
:falseyThe if only takes a single expression for the "then" and "else". Use do to create larger blocks that are a single expression.
Note that the only reason to do this is if your bodies have side effects! (Why?)
(if (even? 5)
(do (println "even")
true)
(do (println "odd")
false))when is an if with only a then branch. It checks a condition and then evaluates any number of statements as a body (so no do is required). The value of the last expression is returned. If the condition is false, nil is returned.
when communicates to a reader that there is no "else" branch.
(when (neg? x)
(throw (RuntimeException. (str "x must be positive: " x))))cond is a series of tests and expressions. Each test is evaluated in order and the expression is evaluated and returned for the first true test.
(let [x 5]
(cond
(< x 2) "x is less than 2"
(< x 10) "x is less than 10"))If no test is satisfied, nil is returned. A common idiom is to use a final test of :else. Keywords (like :else) always evaluate to true so this will always be selected as a default.
(let [x 11]
(cond
(< x 2) "x is less than 2"
(< x 10) "x is less than 10"
:else "x is greater than or equal to 10"))case compares an argument to a series of values to find a match. This is done in constant (not linear) time! However, each value must be a compile-time literal (numbers, strings, keywords, etc).
Unlike cond, case will throw an exception if no value matches.
user=> (defn foo [x]
(case x
5 "x is 5"
10 "x is 10"))
#'user/foo
user=> (foo 10)
x is 10
user=> (foo 11)
IllegalArgumentException No matching clause: 11-
Iterates over a sequence
-
If a lazy sequence, forces evaluation
-
Returns
nil
user=> (doseq [n (range 3)]
(println n))
0
1
2
nil-
List comprehension, not a for-loop
-
Generator function for sequence permutation
-
Bindings behave like
doseq
user=> (for [letter [:a :b]
number (range 3)] ; list of 0, 1, 2
[letter number])
([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])-
Clojure provides recur and the sequence abstraction
-
recuris "classic" recursion-
Consumers don’t control it, considered a lower-level facility
-
-
Sequences represent iteration as values
-
Consumers can partially iterate
-
-
Reducers represent iteration as function composition
-
Added in Clojure 1.5, not covered here
-
-
Functional looping construct
-
loopdefines bindings -
recurre-executesloopwith new bindings
-
-
Prefer higher-order library functions instead
(loop [i 0]
(if (< i 10)
(recur (inc i))
i))-
Function arguments are implicit
loopbindings
(defn increase [i]
(if (< i 10)
(recur (inc i))
i))-
try/catch/finallyas in Java
(try
(/ 2 1)
(catch ArithmeticException e
"divide by zero")
(finally
(println "cleanup")))(try
(throw (Exception. "something went wrong"))
(catch Exception e (.getMessage e)))-
ex-infotakes a message and a map -
ex-datagets the map back out-
Or
nilif not created withex-info
-
(try
(throw (ex-info "There was a problem" {:detail 42}))
(catch Exception e
(prn (:detail (ex-data e)))))