我们已经知道如何把数据绑定到一个变量上,这给我们提供了一种可共享的数据的持久化存储方式(数据被绑定到一个变量后,我们是无法对数据本身进行修改的,重新绑定又是另一回事了,和修改数据本身无关)
;;将列表绑定到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)
但是,有时候我们确实需要在数据被共享的时候去修改它。
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"}>
和引用类型(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")