6Seven Languages in Seven Weeks

Clojure

现代Lisp

不可变性持久化数据结构宏STM

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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 和数据库事务有什么相似之处?


6/7Cloj
0%