基本概念
Clojure 语言中没有简单的变量。但 Clojure 却声称最擅长管理状态,为什么?靠的是仔细设计的状态类型们,其中最引人注目和最常用的是 Atom 和 Ref(ClojureScript 中不可用)。
粗看起来,这两个东西区别很大:生成函数不同,操作函数也不同:
类型 | 生成函数 | 操作函数 |
---|---|---|
Atom | atom |
reset! , swap!
|
Ref | ref |
ref-set , alter , commute
|
Ref 还要将操作包装在dosync
中。
但在实际上,它们其实遵循同样的设计思路:所谓状态变量,指的是身份。例如:
(def person-data {:age 30, :salary 5000})
(def a-xiaoming (atom person-data))
(def r-xiaoming (ref person-data))
定义了两种不同的状态身份。它们实际上拥有同一个值:{:age 30}
,这个值是它的状态数据。在 clojure 语言中,状态身份是不会变的(可以理解为类似于 c 的指针),状态数据也不会变化,变化的是两边的联系。例如, 小明的个人数据是 person-data
。这句话中,小明这个主体状态身份是不会变的,person-data
这个状态数据也不会变化,但小明年龄可能会关联到不同的状态数据,例如,过了年后可能关联为另一个状态数据 {:age 31, :weight 5500}
。
函数推动状态数据的关联变化
推动状态的身份和数据的联系的变化是发生了某动作,也就是函数。因此,驱动状态是这样发生的:
(defn new-year [person-data]
(-> person-data
(update :age inc)
(update :salary #(* % 2))))
(swap! a-xiaoming #(update % :age inc :weight new-year)
注意,一个事件发生的函数应该是统一在一起的,它们是一个事务。clojure 的优秀数据结构保证这个事务的原子性。
下面这种写法呢?
(-> a-xiaoming
(swap! #(update % :age inc))
(swap! #(update :salary (fn [n] (* n 2)))))
事实上,对一个状态数据的连续操作一定是不正确的,它打破了事务的边界。ref 的dosync
建立的边界也是一样。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。