Clojure - a whirlwind tour
Clojure is a functional, dynamic language, a dialect of Lisp that runs on the JVM. Because it is a functional language, it has some aspects we can talk about:
- Immutable data
- Higher-order function
- Anonymous function
- Recursive
- Lazy evaluation
My first impression is that Clojure exclusively uses prefix notation, instead of infix notation like other popular languages.
For example, to evaluate expression 1 + 2 * 3
, we must write:
(+ 1 (* 2 3))
; => 7
All operations in Clojure take the form opening parenthesis, operator, operands, closing parenthesis:
(max 3 5)
; => 5
(println "result is:" (* 2 4))
Define global var
Clojure has no declared and assigned variable syntax like procedure language, instead, Clojure uses def
keyword to define a constant:
(def number-of-days 10)
Function definition
The general form of a function definition is:
(defn (<function name> [parameters]) <function body>)
For example:
(defn add [x y]
(+ x y))
Calling function
(add 3 6)
Anonymous function
An anonymous function (or lambda function) can be created using the fn
:
(fn [parameters list] <function body>)
Calling an anonymous function
((fn [x y] (+ x y)) 2 4)
;=> 8
We can define an anonymous function and bind it to a name using def
keyword
(def multiply (fn [a, b] (* a b)))
(multiply 4 5) ;=> 20
is equivalent to
(defn multiply [a, b] (* a b))
Higher-order function
A Higher-order function is a function that:
- takes one or more functions as arguments.
- returns a function as its result.
In Clojure, some well-known higher-order functions are map
, reduce
, filter
, ...
map
function take two arguments: a function and a collection
(map inc [2, 4, 5, 8, 9])
;=> (3 5 6 9 10)
reduce
function is another well-known higher-order function
(reduce + (range 10))
;=> 45
Compose reduce
with count
function to calculate averages of numbers in the list:
(def sum #(reduce + %))
(def avg #(/ (sum %) (count %)))
(avg [3 4 10])
;=> 8
View more about writing and using the higher-order function in Clojure at https://clojure.org/guides/higher_order_functions
Recursive
Because of immutable data, Clojure doesn't have a built-in for
or while
loop like imperative languages. In Clojure looping and iteration are replaced/implemented via recursive function calls。
The following code uses recursive to calculate the sum of numbers in a list:
(defn recursive-sum [nums]
(if (empty? nums)
0
(+ (first nums) (recursive-sum (rest nums)))
)
)
In practice, we should use built-in reduce
or map
functions to work on collection data:
(defn reduce-sum [nums]
(reduce + nums)
)
Tail-call optimization:
Since Clojure runs on top of JVM, and currently the JVM does not support tail recursion, it can not make the tail call optimization. Instead, it provides the recur
special operator
(defn factorial [n]
(loop [cnt n
acc 1]
(if (< cnt 2) acc
(recur (dec cnt) (* cnt acc)))))
Lazy evaluation
Clojure doesn't not lazy evaluation by default, like Haskell. Instead, Clojure supports lazily evaluated sequences.
Some functions like clojure.core/lazy-seq
can be used to produce lazy sequences. For example, the following function produces a lazy sequence of Fibonacci numbers:
(defn fib-seq
"Returns a lazy sequence of Fibonacci numbers"
([]
(fib-seq 0 1))
([a b]
(lazy-seq
(cons b (fib-seq b (+ a b))))))
Even though this sequence is infinite, taking the first N elements from it does return successfully:
(take 12 (fib-seq))
;=> (1 1 2 3 5 8 13 21 34 55 89 144)