Clojure

20.1 Philosophy

Clojure (Rich Hickey, 2007) is a modern Lisp hosted on the JVM (and on JavaScript as ClojureScript, on .NET as ClojureCLR, and natively via Babashka/GraalVM). Its emphasis is immutable persistent data structures, functional programming, concurrency, and pragmatic host interop.

20.2 Data and Sequences

;; Literals: list, vector, map, set, keyword, regex, char
'(1 2 3)  [1 2 3]  {:a 1 :b 2}  #{1 2 3}  :keyword  #"\d+"  \c

(conj [1 2] 3)                    ; [1 2 3] (vectors add at the end)
(conj '(1 2) 0)                   ; (0 1 2) (lists add at the front)
(get-in {:user {:name "Ana"}} [:user :name])        ; "Ana"
(assoc-in {:user {:name "Ana"}} [:user :age] 30)
(update-in {:counts {:a 1}} [:counts :a] inc)

;; The sequence abstraction: works on every collection
(map inc [1 2 3])                 ; (2 3 4)
(filter even? #{1 2 3 4})
(take 5 (iterate #(* 2 %) 1))     ; (1 2 4 8 16): lazy infinite sequence
(group-by count ["a" "bb" "cc"])  ; {1 ["a"], 2 ["bb" "cc"]}
(frequencies "mississippi")       ; {\m 1, \i 4, \s 4, \p 2}
(into {} (map (fn [x] [x (* x x)]) (range 3)))   ; {0 0, 1 1, 2 4}

;; Destructuring
(let [[a b & more] [1 2 3 4]
      {:keys [name age] :or {age 0}} {:name "Ana"}]
  [a b more name age])            ; [1 2 (3 4) "Ana" 0]

(defn greet [{:keys [name greeting] :or {greeting "Hello"}}]
  (str greeting ", " name "!"))
(greet {:name "Bo"})              ; "Hello, Bo!"

20.3 Polymorphism: Protocols, Records, Multimethods

(defprotocol Shape
  (area [this])
  (perimeter [this]))

(defrecord Circle [r]
  Shape
  (area [_] (* Math/PI r r))
  (perimeter [_] (* 2 Math/PI r)))

(defrecord Rect [w h]
  Shape
  (area [_] (* w h))
  (perimeter [_] (* 2 (+ w h))))

(map area [(->Circle 1) (->Rect 2 3)])   ; (3.14159... 6)

(defmulti speak :type)                    ; dispatch on any function of the arguments
(defmethod speak :dog [_] "Woof")
(defmethod speak :cat [_] "Meow")
(defmethod speak :default [_] "...")
(speak {:type :dog})                      ; "Woof"

20.4 State and Concurrency

(def counter (atom 0))                    ; atom: uncoordinated, synchronous
(swap! counter inc)                       ; 1
(reset! counter 10)
@counter                                  ; 10 (deref)

(def account-a (ref 100))                 ; refs: coordinated via STM
(def account-b (ref 0))
(dosync
  (alter account-a - 30)
  (alter account-b + 30))

(def logger (agent []))                   ; agents: asynchronous
(send logger conj "event")

(def result (future (Thread/sleep 100) 42))   ; runs on another thread
@result                                   ; 42

(require '[clojure.core.async :refer [chan go >! <! <!! timeout]])
(let [c (chan)]
  (go (>! c "hello from a go block"))
  (<!! c))                                ; CSP-style channels (core.async)

20.5 Interop, Tooling and Ecosystem

Tools: deps.edn + Clojure CLI, Leiningen, Babashka (fast-starting scripts), clj-kondo (linter), Calva/CIDER/Cursive, nREPL.

Ecosystem: Ring/Compojure/Reitit (web), Pedestal, Kit, next.jdbc (SQL), HoneySQL, Datomic/XTDB/Datalevin (databases), clojure.spec/Malli (data specs and validation), re-frame/Reagent (ClojureScript UIs), shadow-cljs.

(.toUpperCase "hello")                    ; Java method call
(Math/sqrt 16)                            ; static method
(java.util.UUID/randomUUID)
(doto (java.util.ArrayList.) (.add 1) (.add 2))