Using bindings of vars, conditions, loops, and error handling
In this recipe, we will review Clojure programming control structures related to vars and values, conditions, iterations, and loops. We will use the following special forms, macros, and functions:
def
andlet
if
andif-not
when
andwhen-not
case
andcond
do
anddotimes
loop
andrecur
- try... catch... throw
Getting ready
You only need REPL, as described in the first recipe in this chapter, and no additional libraries. Start REPL so that you can test the sample code immediately in this recipe.
How to do it...
Let's start with how to use def
and let
to bind vars.
def and let
def
is a special form that binds symbols in the global scope in their namespace. def
requires var and value:
(def var val)
This sample binds x
to 100
:
(def x 100) ;;=> 100
Whereas let
binds symbols in its local scope. You can put multiple expressions in a let
clause. let
evaluates them and returns the last expression:
(let [var-1 val-1 var-2 val-2 ...] expr-1 expr-2 .... )
In this example, let binds x
to 3
and y
to 2
. Then, it evaluates two expressions consecutively and returns the second expression:
(let [x 3 y 2] (println "x = " x ", y = " y) (* x y) ) ;;=> x = 3 , y = 2 ;;=> 6
if and if-not
if
takes three arguments; the third argument (else-expression
) is optional:
(if condition then-expression else-expression)
In the next example, the code returns the absolute value of numbers:
(let [x 10] (if (> x 0) x (- x)) ) ;;=> 10 (let [x -10] (if (> x 0) x (- x)) ) ;;=> 10
If there's no third parameter and if the test results as false, it returns nil
:
(let [x -10] (if (> x 0) x)) ;;=> nil
if-not
is opposite to if
. It returns then-expression
if the test fails:
(if-not condition then-expression else-expression)
The example should return false
:
(if-not true true false) ;;=> false
when and when-not
The when
function is similar to if
, but it evaluates one or more expressions if the condition is evaluated to true; otherwise, it is false:
(when condition expr-1 expr-2 ...)
The first expression prints out x = 10
and returns 100
. The second one only returns nil
:
(let [x 10] (when (> x 0) (println "x = " x) (* x x))) ;;=> x = 10 ;;=> 100 (let [x -10] (when (> x 0) (println "x = " x) (* x x))) ;;=> nil
when-not
is the opposite of when
:
(when-not condition expr-1 expr-2 expr 3 ...)
The next code uses when-not
and does the same thing as the preceding function:
(let [x 10] (when-not (<= x 0) (println "x = " x) (* x x))) ;;=>x = 10 ;;=> 100 (let [x -10] (when-not (<= x 0) (println "x = " x) (* x x))) ;;=> nil
case and cond
case
tests whether there is a matched value. If so, case
evaluates the corresponding expression. If there is no matched value, it returns otherwise-value
. If there is no otherwise-value
specified, it returns nil
:
(case condition value1 expr-1 value2 expr-2 otherwise-value )
In the first expression, the condition matches the value 2
and "two"
is returned. The second expression returns a string, "otherwise"
:
(let [x 2] (case x 1 "one" 2 "two" 3 "three" "otherwise" )) ;;=> "two" (let [x 4] (case x 1 "one" 2 "two" 3 "three" "otherwise" )) ;;=> "otherwise"
cond
is a macro and is similar to case. cond
has been heavily used in the Lisp language. cond
is more flexible than case.
cond
takes a set of condition/expr pairs and evaluates each condition. If one of the conditions is true, cond
evaluates the corresponding expression and returns it. Otherwise, it returns the expression of :else
:
(cond condition-1 expr-1 condition-2 expr-2 ... :else expr-else )
The next sample code acts the same as the preceding one:
(let [x 10] (cond (= x 1) "one" (= x 1) "two" (= x 3) "three" :else "otherwise" ) )
do and dotimes
do
evaluates the expressions in order and returns the last:
(do expr-1 expr-2 ...)
In the next sample, do
evaluates the first expression and prints x = 10
, then it evaluates the second and returns 11
:
(def x 10) ;;=> #'living-clojure.core/x (do (println "x = " x) (+ x 1)) ;;=> x = 10 ;;=> 11
dotimes
repeats the expression while var
increments from 0 to (number-exp - 1):
(dotimes [var number-exp] expression )
This example prints the square of x
where x
is 0 to 4:
(dotimes [x 5] (println "square : " (* x x))) ;;=> square : 0 ;;=> square : 1 ;;=> square : 4 ;;=> square : 9 ;;=> square : 16
loop and recur
You may sometimes want to write a program that loops with a condition. Since Clojure is an immutable language, you cannot change a loop counter, unlike in imperative languages such as Java.
The combination of loop
and recur
is used in such a situation. Their forms are as follows:
(loop [var-1 val-1 var-2 val-2 ...] expr-1 expr-2 ... ) (recur expr-1 expr-2 ... )
The next very simple example shows how loop
and recur
work. In the loop, x
is set to 1
and increased until it is smaller than 5:
(loop [x 1] (when (< x 5) (println "x = " x) (recur (inc x)) ) ) ;;=> x = 1 ;;=> x = 2 ;;=> x = 3 ;;=> x = 4 ;;=> nil
The next example calculates the sum of 1 to 10 using loop
and recur
:
(loop [x 1 ret 0] (if (> x 10) ret (recur (inc x) (+ ret x)) ) ) ;;=> 55
try... catch... throw
Clojure uses an error handler borrowed from Java:
(try exp-1 exp 2 ... (catch class-of-exception var exception (finally finally-expr) )
Inside try
, there are one or more expressions. finally
is optional. The following example emits an exception and returns a string generated in the catch
:
(try (println "Let's test try ... catch ... finally") (nth "Clojure" 7) (catch Exception e (str "exception occured: " (.getMessage e))) (finally (println "test finished")) ) ;;=> Let's test try ... catch ... finally ;;=> test finished ;;=> "exception occured: String index out of range: 7"
How it works...
Clojure's lexical scope hides the outside bindings of vars inside bindings of vars. The next example shows the scopes of a nested let
. The inside let
binds x
to 10
and y
to 10
. Thus, inside println
prints 100
. Similarly, the outside let
binds x
to 3
and y
to 2
. Thus, it prints 6
:
(let [x 3 y 2] (let [x 10 y 10] (println "inside : " (* x y)) ) (println "outside : " (* x y)) ) ;;=> inside : 100 ;;=> outside : 6 ;;=> nil
Similarly, a local binding of a var hides the global binding of a var:
(def x 1) ;;=> #'living-clojure/x ;;=> 1 (println "global x = " x) ;;=> global x = 1 (let [x 10] (println "local x = " x)) ;;=>local x = 10 (println "global x = " x) ;;=> global x = 1
The when
is a macro using the if
special form. You can see how the when
is defined using macroexpand
:
(macroexpand '(when (> x 0) (println "x = " x) (* x x))) ;;=> (if (> x 0) (do (println "x = " x) (* x x)))
if-not
is also a macro using if
:
(macroexpand '(if-not true true false)) ;;=> (if (clojure.core/not true) true false)