clojure 新手指南(15):可变性

我们已经知道如何把数据绑定到一个变量上,这给我们提供了一种可共享的数据的持久化存储方式(数据被绑定到一个变量后,我们是无法对数据本身进行修改的,重新绑定又是另一回事了,和修改数据本身无关)

;;将列表绑定到lat上
user=> (def lat (list 1 2 3))
#'user/lat

user=> lat
(1 2 3)

;;我们得到的是一个新的列表
user=> (cons 3 lat)
(3 1 2 3)

;;原来列表并没有改变
user=> lat
(1 2 3)

但是,有时候我们确实需要在数据被共享的时候去修改它。


事物型引用(Transactional References)


Clojure 提供了接口用于协调一个对象的并发修改。通过事务型引用(在clojure中对应着Ref类型),clojure相当于创建了一个管卡,每次只允许一个事务(类似关系数据库中的事务概念)通过这个关卡,并且一个事务中的所有改变要么同时生效,要么回滚(这就是clojure软件事务内存STM的概念)。

包装引用对象

我们可以使用ref函数讲一个普通对象包装成Ref类型对象

;;包装一个空的哈希表
=>(ref (hash-map))
#<Ref@52879daa: {}>

;;绑定一个Ref类型对象,该Ref包装了一个哈希表
=>(def vehicles (ref {:truck "Toyota" :car "Subaru" :plane "de Havilland"}))
#'user/vehicles

=>vehicles
#<Ref@14325ad8: {:truck "Toyota", :car "Subaru", :plane "de Havilland"}>

解引用对象

接上面的例子,我们已经有了一个被包装在Ref类型之下的一个对象vehicles,但有很多函数需要访问被包装的对象,而不是这个引用对象:

;; vehicles 不是一个map,所以会报错
=>(keys vehicles)
java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Ref
 
=>(map? vehicles)
false

;;vehicles是Ref类型对象
=>(class vehicles)
clojure.lang.Ref
我们可以使用defref函数来获取封装在Ref内部的对象,当然还有一种更简洁的方式就是使用@符号:
=>(deref vehicles)
{:truck "Toyota", :car "Subaru", :plane "de Havilland"}

=>@vehicles
{:truck "Toyota", :car "Subaru", :plane "de Havilland"}

=>(map? @vehicles)
true

=>(keys @vehicles)
(:truck :car :plane)

=>(vals @vehicles)
("Toyota" "Subaru" "de Havilland")

修改引用对象

我们引用对象的目的,就是想修改它。之所以叫作事务型引用,是因为我们必须在一个事务中去修改它。在事务中(例如dosync)使用alter函数是最安全的修改方式,它能确保我们在事务操作期间,其他对这个对象的改变都不会发生。

;;使用dosync可以理解成开启了一个事务
=>(dosync
    (alter vehicles assoc :car "Volkswagon"))
{:truck "Toyota", :car "Volkswagon", :plane "de Havilland"}

;;vehicles是真的被改变了,而不是返回一个新的对象
=>@vehicles
{:truck "Toyota", :car "Volkswagon", :plane "de Havilland"}

;;使用alter删除map的一个键值对
=>(dosync
    (alter vehicles dissoc :car))
{:truck "Toyota", :plane "de Havilland"}

;;修改在当前对象上生效了
=>@vehicles
{:truck "Toyota", :plane "de Havilland"}

如果你不关心引用对象原来的值的话,可以使用ref-set来设置一个新的值

=>(dosync
    (ref-set vehicles {:motorcycle "Ducati"}))
{:motorcycle "Ducati"}

=>vehicles
#<Ref@229ec9cd: {:motorcycle "Ducati"}>



原子类型(Atoms)


和引用类型(Ref)类似,原子也是创建一个管卡来修改一个不可变对象,并且原子也能和Ref一样进行同步更新。但是原子并不要求在事务中运行,并且它们不能协调多个状态的更新。(多个Ref类型对象可以在一个事务中协调更新,要么同时成功,要么同时失败,即回滚)

包装原子对象

操作和ref类似

;;绑定一个原子对象,该原子对象包装了一个哈希表
=>(def vehicles (atom ["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver"]))
#'user/vehicles

=>vehicles
#<Atom@31f3f16b: ["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver"]>

解引用原子对象

这个和ref一样

=>(deref vehicles)
["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver"]

=>@vehicles
["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver"]

修改原子对象

我们可以使用swap!函数或者reset!函数(名字后面有感叹号在lisp方言中代表修改函数之意)来修改被原子包装的对象。swap!用于在原来值的基础上进行修改,reset!则是直接用新值替换原来的值。

=>(swap! vehicles conj "Ducati Diavel")
["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver" "Ducati Diavel"]

=>@vehicles
["Toyota Tacoma" "Subaru Outback" "de Havilland Beaver" "Ducati Diavel"]

=>(reset! vehicles (take 2 @vehicles))
("Toyota Tacoma" "Subaru Outback")

=>@vehicles
("Toyota Tacoma" "Subaru Outback")

你可能感兴趣的:(java,lisp,clojure)