为何同一函数中存在loop/recur时,Clojure的for表达式无输出?
Clojure中loop/recur导致for表达式无输出的原因
函数定义代码
(defn foo [] (let [line (apply str (repeat 2 \.)) lines (into [] (repeat 2 line))] (for [x lines] (println x)) ; 此代码无输出 (loop [remaining [[1 2] [3 4]]] (if (empty? remaining) (for [y lines] (println y)) ; 此代码有输出 (recur (rest remaining))))))
调用foo的输出
.. .. (nil nil)
问题
预期输出应为四次..加(nil nil)。移除整个loop/recur代码块后,原无输出的for表达式可正常输出。请问为何loop/recur会导致for表达式无法生成输出?
原因解析
Clojure里的for是惰性序列生成器——它不会立刻执行里面的代码,只有当序列被"消费"(比如被遍历、打印、存入集合)的时候,才会逐个计算元素并执行对应的操作。
当函数里存在loop/recur块时:
- 第一个
(for [x lines] (println x))执行后生成了一个惰性序列,但这个序列没有被任何代码消费(既没被绑定到变量,也没作为返回值传递,更没被遍历),所以里面的println根本不会触发。 - 函数继续执行loop/recur,直到循环结束后返回第二个
for生成的惰性序列。而REPL在调用函数后会自动打印返回值,这就相当于消费了第二个惰性序列,所以里面的println会执行,输出两行..,最后打印序列本身(nil nil)(因为每个println返回nil)。
- 第一个
移除loop/recur块后:
- 函数的最后一个表达式变成了第一个
for,函数返回这个惰性序列。REPL自动打印返回值,消费了这个序列,所以里面的println触发,输出两行..,同时打印序列(nil nil)。
- 函数的最后一个表达式变成了第一个
如果想让第一个for也执行,你需要主动消费它,比如用doall包裹:
(doall (for [x lines] (println x)))
这样不管它是不是最后一个表达式,都会立刻执行里面的操作。
内容的提问来源于stack exchange,提问作者user1785730
相关产品推荐
相关产品推荐

