Clojure与Leiningen:此示例为何需doall?宏调用doall为何无效?
关于Clojure中
doall必要性和宏调用无输出的问题解答 一、为什么lein run里需要doall包裹repeat函数?
咱们先拆解核心原因:Clojure的for生成的是惰性序列——序列里的元素不会立刻求值,只有当你主动去消费(比如遍历、提取元素)的时候,才会逐个触发计算。
- 在REPL环境中,当你执行
(repeat println 2),REPL为了把结果展示给你,会自动遍历整个惰性序列,这就触发了序列里每一个(f)的调用(也就是两次println),所以你能看到输出。 - 但
lein run是批处理运行模式,它不会自动去消费惰性序列。你的repeat函数返回的只是一个未被激活的惰性序列,里面的(f)调用根本没被执行,自然看不到任何输出。doall的作用就是强制遍历整个序列,触发所有元素的求值,把println这种副作用给执行出来。
其实你这个场景用dotimes会更直接,比如(defn repeat [f n] (dotimes [_ n] (f))),它本身就是立即执行的,不需要doall,不过重点还是得理解惰性序列在不同运行环境下的差异~
二、为什么宏调用加doall也没输出?
问题出在你定义宏的写法上,咱们先展开看看宏生成的代码:
- 对于
newPrint1,当你调用(newPrint1 1 2 3),宏展开后是'(println (1 2 3))——注意这里的'是quote(引用),它把整个(println ...)变成了一个纯数据(列表),而不是要执行的代码! - 同理,
newPrint2展开后是'(println 1 2 3),同样是被引用的列表,只是参数用了~@展开,但外层的quote还是把它变成了静态数据,不是可执行的表达式。
所以在REPL里执行(newPrint1 1 2 3),你看到的输出(clojure.core/println (1 2 3))其实是REPL在打印这个被引用的列表数据,而不是执行println的结果!而doall是用来处理序列的,你的宏返回的是单个引用列表,不是序列,而且就算是序列,里面的元素也是数据,不是可执行的副作用代码,自然起不到作用。
要让宏真正执行println,你得去掉外层的quote:
(defmacro newPrint1 [& args] `(println ~args)) (defmacro newPrint2 [& args] `(println ~@args))
这样调用(newPrint1 1 2 3)会展开成(println (1 2 3)),(newPrint2 1 2 3)展开成(println 1 2 3),这时候才会真正执行println输出内容。
内容的提问来源于stack exchange,提问作者buddingprogrammer
相关产品推荐
相关产品推荐

