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:
defandletifandif-notwhenandwhen-notcaseandconddoanddotimesloopandrecur- 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)