状态与标识 #

一、状态与标识的概念 #

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))))

优先使用不可变操作。

九、总结 #

状态与标识要点:

概念 说明
不可变数据
标识 状态容器
状态 标识在某一时刻的值
时间 状态变化的维度

关键点:

  1. 值是不可变的,状态是标识在某一时刻的值
  2. 持久化数据结构实现高效不可变
  3. 选择合适的引用类型管理状态
  4. 最小化可变状态
  5. 优先使用纯函数

高级特性系列完成!接下来学习工具与生态!

最后更新:2026-03-27