Clojure的dosync是正则序?

  解释器求值的顺序可以分为应用序和正则序,应用序是先求值参数,再执行表达式;正则序则是先将表达式按照实际参数展开,然后再执行。具体可以看看过去写的这篇文章

   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 
  

时间: 2024-09-23 14:56:34

Clojure的dosync是正则序?的相关文章

Clojure的并发(一) Ref和STM

Clojure 的并发(一) Ref和STM Clojure 的并发(二)Write Skew分析Clojure 的并发(三)Atom.缓存和性能 Clojure 的并发(四)Agent深入分析和Actor Clojure 的并发(五)binding和let Clojure的并发(六)Agent可以改进的地方Clojure的并发(七)pmap.pvalues和pcallsClojure的并发(八)future.promise和线程     Clojure处理并发的思路与众不同,采用的是所谓STM

Clojure的并发(四)Agent深入分析和Actor

Clojure 的并发(一) Ref和STM Clojure 的并发(二)Write Skew分析Clojure 的并发(三)Atom.缓存和性能 Clojure 的并发(四)Agent深入分析和Actor Clojure 的并发(五)binding和let Clojure的并发(六)Agent可以改进的地方Clojure的并发(七)pmap.pvalues和pcallsClojure的并发(八)future.promise和线程 四. Agent和Actor    除了用于协调同步的Ref,独

Clojure世界: STM的统计

   年前一篇blog提过,写了一个stm-profiler用于统计clojure STM的运行状况,放在了github上:https://github.com/killme2008/stm-profiler    STM的事务在遇到写冲突(多个事务写同一个ref的时候)就会回滚事务并重试,通过stm-profiler你可以查看事务的重试次数,重试原因,以及每个reference的使用情况.使用很简单,在lein的project.clj引用stm-profiler: [stm-profiler 

Clojure世界:日志管理——clojure.tools.logging

   处理日志是任何一个产品级的程序都需要仔细处理的模块.在Java中,我们经常使用的是log4j就是一个日志框架.在clojure里,同样有一套日志框架--clojure.tools.logging,它不仅提供了常用的日志输出功能,还屏蔽了Java各种日志框架之间的差异,如slf4j,commons-logging,log4j,java.util.logging等,让你可以透明地使用这些框架来处理日志. 名称:clojure.tools.logging 主页:https://github.co

Programming clojure – Concurrency

Clojure的并发, 这兄弟写的比较系统, http://www.blogjava.net/killme2008/archive/2010/07/archive/2010/07/14/326027.html   Concurrency is a fact of life and, increasingly, a fact of software. 为什么需要并发? • Expensive computations may need to execute in parallel on multi

《Clojure数据分析秘笈》——3.2节使用STM管理程序复杂度

3.2 使用STM管理程序复杂度Clojure并发特性的基础是它的STM系统.这基本上是将数据库事务的语义扩展至计算机内存中.STM工作方式是使用ref函数标记由STM控制的内存位置.可以使用deref函数或者@宏在任意位置对那些内存位置进行反引用.但是仅能修改在dosync块内部引用的值.接着,当事务执行完毕后,STM执行检查.如果事务改变的引用被另一个事务修改,事务失败,并排队进行重试.然而,如果没有引用改变,事务成功并提交.当在事务中时,如果在外部编程,这些值不会改变.一旦事务提交,使用r

Clojure的并发(二)Write Skew分析

Clojure 的并发(一) Ref和STM Clojure 的并发(二)Write Skew分析Clojure 的并发(三)Atom.缓存和性能 Clojure 的并发(四)Agent深入分析和Actor Clojure 的并发(五)binding和let Clojure的并发(六)Agent可以改进的地方Clojure的并发(七)pmap.pvalues和pcallsClojure的并发(八)future.promise和线程      在介绍Ref的上一篇blog提到,基于snapshot

Clojure的并发(三)Atom、缓存和性能

Clojure 的并发(一) Ref和STM Clojure 的并发(二)Write Skew分析Clojure 的并发(三)Atom.缓存和性能 Clojure 的并发(四)Agent深入分析和Actor Clojure 的并发(五)binding和let Clojure的并发(六)Agent可以改进的地方Clojure的并发(七)pmap.pvalues和pcallsClojure的并发(八)future.promise和线程 三.Atom和缓存     Ref适用的场景是系统中存在多个相互

Clojure世界:Http Client

    使用http client提交表单或者下载网页也是非常常见的任务,比如使用Java的时候可以用标准库的HttpURLConnection,也可以选择Apache Http Client.在clojure里也有这样的类库,这里我将介绍三个各有特色的http client实现.     首先,我最先推荐使用clj-http这个类库,它是Apache HttpClient的clojure wrapper,是一个提供同步API的简单易用的Http Client. 名称: clj-http 主页: