状态与标识 #
一、状态与标识的概念 #
Clojure对状态和标识有独特的理解。理解这些概念是掌握Clojure并发模型的关键。
1.1 核心定义 #
| 概念 | 定义 |
|---|---|
| 值 | 永恒不变的量 |
| 标识 | 随时间变化的实体 |
| 状态 | 标识在某一时刻的值 |
| 时间 | 状态变化的维度 |
1.2 传统模型 vs Clojure模型 #
传统面向对象:
java
Person person = new Person("Alice", 30);
person.setAge(31); // 状态被修改
Clojure模型:
clojure
(def person (atom {:name "Alice" :age 30}))
(swap! person update :age inc) ; 新状态替换旧状态
二、值语义 #
2.1 值是不可变的 #
clojure
(def v [1 2 3])
(conj v 4)
v
值永远不会改变,"修改"操作返回新值。
2.2 值相等性 #
clojure
(= [1 2 3] [1 2 3])
(= {:a 1 :b 2} {:b 2 :a 1})
(= #{1 2 3} #{3 2 1})
值相等基于内容,而非引用。
2.3 值的优势 #
- 无需锁保护
- 易于推理
- 可安全共享
- 支持时间旅行
三、标识与状态 #
3.1 标识是状态容器 #
clojure
(def counter (atom 0))
@counter
(swap! counter inc)
@counter
counter 是标识,@counter 是当前状态。
3.2 引用类型 #
| 类型 | 特点 | 用途 |
|---|---|---|
| Atom | 独立、同步 | 简单状态 |
| Ref | 协调、同步、事务 | 多变量协调 |
| Agent | 独立、异步 | 后台任务 |
| Var | 线程局部 | 函数参数 |
3.3 状态变化模型 #
clojure
(def account (ref {:balance 1000 :transactions []}))
(dosync
(alter account
(fn [state]
(-> state
(update :balance - 100)
(update :transactions conj {:type :withdraw :amount 100})))))
@account
四、持久化数据结构 #
4.1 结构共享 #
clojure
(def v1 [1 2 3 4 5])
(def v2 (assoc v1 2 :x))
v1
v2
新旧数据共享未修改的部分。
4.2 高效更新 #
clojure
(def m1 {:a 1 :b 2 :c 3 :d 4 :e 5})
(def m2 (assoc m1 :f 6))
(count (keys m1))
(count (keys m2))
添加元素不需要复制整个数据结构。
4.3 时间复杂度 #
| 操作 | 复杂度 |
|---|---|
| 访问 | O(log32 n) ≈ O(1) |
| 更新 | O(log32 n) |
| 添加 | O(log32 n) |
4.4 内存效率 #
clojure
(def base (vec (range 1000000)))
(def derived (assoc base 0 :x))
(System/identityHashCode base)
(System/identityHashCode derived)
大部分数据被共享。
五、时间与变化 #
5.1 状态历史 #
clojure
(def history (atom []))
(defn record-state [state]
(swap! history conj {:time (System/currentTimeMillis)
:state state}))
(def counter (atom 0))
(add-watch counter :history
(fn [_ _ _ new-state]
(record-state new-state)))
(swap! counter inc)
(swap! counter inc)
(swap! counter inc)
@history
5.2 时间线 #
clojure
(defn timeline [ref]
(let [states (atom [])]
(add-watch ref :timeline
(fn [_ _ old new]
(swap! states conj {:from old :to new :time (System/currentTimeMillis)})))
@states))
5.3 快照 #
clojure
(defn snapshot [ref]
{:value @ref
:time (System/currentTimeMillis)})
(def state (atom {:users [] :items []}))
(def snap1 (snapshot state))
(swap! state update :users conj {:id 1 :name "Alice"})
(def snap2 (snapshot state))
[snap1 snap2]
六、状态管理模式 #
6.1 单一状态原子 #
clojure
(def app-state (atom {:users {}
:items {}
:settings {}}))
(defn add-user [id user]
(swap! app-state assoc-in [:users id] user))
(defn get-user [id]
(get-in @app-state [:users id]))
(add-user 1 {:name "Alice" :email "alice@example.com"})
(get-user 1)
6.2 分离关注点 #
clojure
(def users (atom {}))
(def items (atom {}))
(def settings (atom {:theme :light}))
(defn update-user [id f]
(swap! users update id f))
(defn change-theme [new-theme]
(swap! settings assoc :theme new-theme))
6.3 协调状态(Ref) #
clojure
(def accounts (ref {}))
(def transactions (ref []))
(defn transfer [from-id to-id amount]
(dosync
(let [from-balance (get @accounts from-id 0)
to-balance (get @accounts to-id 0)]
(when (>= from-balance amount)
(alter accounts -> (assoc from-id (- from-balance amount))
(assoc to-id (+ to-balance amount)))
(alter transactions conj {:from from-id :to to-id :amount amount})))))
七、实践示例 #
7.1 购物车 #
clojure
(def cart (atom {:items [] :total 0}))
(defn add-item [item price quantity]
(swap! cart
(fn [state]
(-> state
(update :items conj {:item item :price price :qty quantity})
(update :total + (* price quantity))))))
(defn remove-item [item]
(swap! cart
(fn [state]
(let [items (filter #(not= (:item %) item) (:items state))
total (reduce + (map #(* (:price %) (:qty %)) items))]
{:items items :total total}))))
(add-item "Apple" 1.5 3)
(add-item "Banana" 0.8 5)
@cart
7.2 游戏状态 #
clojure
(def game (atom {:players {}
:board {}
:turn :player1}))
(defn add-player [id name]
(swap! game assoc-in [:players id] {:name name :position [0 0]}))
(defn move-player [id direction]
(swap! game
(fn [state]
(let [current (get-in state [:players id :position])
new-pos (case direction
:up (update current 1 inc)
:down (update current 1 dec)
:left (update current 0 dec)
:right (update current 0 inc))]
(assoc-in state [:players id :position] new-pos)))))
(add-player :p1 "Alice")
(move-player :p1 :right)
@game
7.3 事件溯源 #
clojure
(def events (atom []))
(def state (atom {}))
(defn emit-event [event]
(swap! events conj event)
(apply-event @state event))
(defn apply-event [state event]
(case (:type event)
:user-created (assoc state (:id event) (:data event))
:user-updated (update state (:id event) merge (:data event))
:user-deleted (dissoc state (:id event))
state))
(defn rebuild-state []
(reset! state (reduce apply-event {} @events)))
(emit-event {:type :user-created :id 1 :data {:name "Alice"}})
(emit-event {:type :user-updated :id 1 :data {:email "alice@example.com"}})
@state
八、最佳实践 #
8.1 最小化可变状态 #
clojure
(defn process-data [data]
(->> data
(filter valid?)
(map transform)
(reduce aggregate {})))
尽量使用纯函数,减少状态。
8.2 状态隔离 #
clojure
(def ui-state (atom {}))
(def data-state (atom {}))
(def config-state (atom {}))
分离不同关注点的状态。
8.3 不可变优先 #
clojure
(defn build-result [data]
(-> data
(assoc :processed true)
(update :count inc)
(assoc :timestamp (System/currentTimeMillis))))
优先使用不可变操作。
九、总结 #
状态与标识要点:
| 概念 | 说明 |
|---|---|
| 值 | 不可变数据 |
| 标识 | 状态容器 |
| 状态 | 标识在某一时刻的值 |
| 时间 | 状态变化的维度 |
关键点:
- 值是不可变的,状态是标识在某一时刻的值
- 持久化数据结构实现高效不可变
- 选择合适的引用类型管理状态
- 最小化可变状态
- 优先使用纯函数
高级特性系列完成!接下来学习工具与生态!
最后更新:2026-03-27