解释器求值的顺序可以分为应用序和正则序,应用序是先求值参数,再执行表达式;正则序则是先将表达式按照实际参数展开,然后再执行。具体可以看看过去写的这篇文章。
Clojure的求值可以肯定是应用序的,如执行
(defn mytest [a b]
(if (= a 0)
a
b))
(mytest 0 1/0)
尽管在(mytest 0 1/0)中a绑定为0,如果求值器是完全展开再求值,那应该正常执行并返回a,也就是1;但是因为clojure是应用序,因此参数b的1/0会先计算,这显然会报错。
clojure的dosync用于将一些表达式包装成事务,Ref的更新操作没有包装在事务里,会抛出异常
;;定义mutable的Ref
(def song (ref #{}))
;;添加一首歌
(alter song conj "dangerous")
alter用于向Ref查询并添加元素,用conj将"dangerous"这首歌加入集合,但是alter要求执行在一个事务里,因此上面的代码会报错
java.lang.IllegalStateException: No transaction running (NO_SOURCE_FILE:0)
如果你用dosync包装就没有问题
user=> (dosync (alter song conj "dangerous"))
#{"dangerous"}
返回更新后的结果集合。这个跟我们要谈的正则序和应用序有什么关系呢?可能你看出来了,如果说clojure是应用序,那么在表达式 (dosync (alter song conj "dangerous"))中,alter也应该先执行,应当照样报" No transaction running"的错误才对,为何却没有呢?难道dosync是按照正则序执行?
查看dosync的文档
user=> (doc dosync)
-------------------------
clojure.core/dosync
([& exprs])
Macro
Runs the exprs (in an implicit do) in a transaction that encompasses
exprs and any nested calls. Starts a transaction if none is already
running on this thread. Any uncaught exception will abort the
transaction and flow out of dosync. The exprs may be run more than
once, but any effects on Refs will be atomic.
这是一个宏,他的作用是将表达式包装在一个事务里,如果当前线程没有事务,那么就启动一个。
查看源码:
(defmacro dosync
"Runs the exprs (in an implicit do) in a transaction that encompasses
exprs and any nested calls. Starts a transaction if none is already
running on this thread. Any uncaught exception will abort the
transaction and flow out of dosync. The exprs may be run more than
once, but any effects on Refs will be atomic."
[& exprs]
`(sync nil ~@exprs))
本质上dosync是调用了sync这个宏,sync干了些什么?
(defmacro sync
"transaction-flags => TBD, pass nil for now
Runs the exprs (in an implicit do) in a transaction that encompasses
exprs and any nested calls. Starts a transaction if none is already
running on this thread. Any uncaught exception will abort the
transaction and flow out of sync. The exprs may be run more than
once, but any effects on Refs will be atomic."
[flags-ignored-for-now & body]
`(. clojure.lang.LockingTransaction
(runInTransaction (fn [] ~@body))))
找到了,原来是调用了clojure.lang.LockingTransaction.runInTransaction这个静态方法,并且将exps包装成一个匿名函数
fn [] ~@body
因此,dosync并非正则序,dosync是个宏,(dosync (alter song conj "dangerous"))展开之后,其实是
(sync nil (fun [] (alter song conj "dangerous")))
这就解答了为什么(dosync (alter song conj "dangerous"))可以正常运行的疑问。宏的使用,首先是展开,然后才是按照应用序的顺序求值。
文章转自庄周梦蝶 ,原文发布时间2010-07-13