· 原发布于 hungle00.github.io

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)