第6章:Clojure(现代Lisp)
一、导读
1.1 本章学习目标
第6章介绍 Clojure——一门运行在 JVM 上的现代 Lisp 方言。Clojure 坚持不可变性和函数式编程,同时提供了强大的宏系统和持久化数据结构。
本章的核心学习目标包括:
- 理解不可变性的价值
- 掌握 Clojure 的持久化数据结构
- 学会使用宏进行元编程
- 理解 STM(软件事务内存)并发模型
1.2 为什么本章如此重要
Clojure 证明了函数式编程可以在工业级应用中使用。它的不可变数据结构、持久化集合、STM 并发模型,都是现代编程语言的重要参考。Clojure 的宏系统展示了"代码即数据"(homoiconicity)的威力。
1.3 核心问题引导
不可变性为什么对并发编程如此重要?
持久化数据结构是如何做到"修改"而不破坏旧版本的?
Clojure 的宏和其他语言的宏有什么区别?
STM 和锁并发相比有什么优势?
二、核心概念详解
2.1 不可变性
clojure
;; 所有数据结构默认不可变
(def v [1 2 3])
(conj v 4) ;; => [1 2 3 4]
v ;; => [1 2 3](原向量未变)
;; "修改"返回新结构
(def v2 (conj v 4))
v2 ;; => [1 2 3 4]
;; 结构共享:新结构和旧结构共享未修改的部分2.2 持久化数据结构
clojure
;; 向量
(def vec1 [1 2 3 4 5])
(nth vec1 2) ;; => 3
(assoc vec1 2 99) ;; => [1 2 99 4 5]
;; Map
(def m {:name "Alice" :age 30})
(get m :name) ;; => "Alice"
(assoc m :email "a@b.com") ;; => {:name "Alice" :age 30 :email "a@b.com"}
;; 集合
(def s #{1 2 3 4 5})
(conj s 6) ;; => #{1 2 3 4 5 6}
(disj s 3) ;; => #{1 2 4 5}2.3 函数式编程
clojure
;; 高阶函数
(map inc [1 2 3]) ;; => (2 3 4)
(filter even? [1 2 3 4 5]) ;; => (2 4)
(reduce + [1 2 3 4 5]) ;; => 15
;; 管道操作(-> 和 ->>)
(->> [1 2 3 4 5]
(filter even?)
(map #(* % %))
(reduce +)) ;; => 20
;; 惰性序列
(take 10 (filter even? (range))) ;; => (0 2 4 6 8 10 12 14 16 18)2.4 宏
clojure
;; 宏在编译时展开
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
(unless false
(println "This will print"))
;; 自定义控制结构
(defmacro with-retry [retries & body]
`(loop [attempt# 0]
(let [result# (try
~@body
(catch Exception e#
(if (< attempt# ~retries)
(recur (inc attempt#))
(throw e#))))]
result#)))2.5 STM(软件事务内存)
clojure
;; Ref:可变引用,通过事务修改
(def balance (ref 100))
;; 事务
(dosync
(alter balance - 50))
;; Agent:异步修改
(def counter (agent 0))
(send counter inc)
(send counter + 5)
;; Atom:原子操作
(def config (atom {:host "localhost" :port 8080}))
(swap! config assoc :debug true)三、实战案例
3.1 案例一:用 Clojure 实现银行账户系统
clojure
(def accounts (ref {}))
(defn create-account [id balance]
(dosync
(alter accounts assoc id (ref balance))))
(defn transfer [from-id to-id amount]
(dosync
(let [from (get @accounts from-id)
to (get @accounts to-id)]
(when (and from to (>= @from amount))
(alter from - amount)
(alter to + amount)
true))))
(create-account :alice 1000)
(create-account :bob 500)
(transfer :alice :bob 200)四、本章小结
不可变性:所有数据默认不可变,"修改"返回新结构。
持久化数据结构:通过结构共享实现高效的不可变集合。
宏:代码即数据,在编译时操作代码的 AST。
STM:事务性的并发修改,避免锁的复杂性。
五、思考与练习
不可变数据结构的性能开销有多大?持久化数据结构如何解决这个问题?
Clojure 的宏和 C 的预处理器宏有什么本质区别?
STM 和数据库事务有什么相似之处?