为何以下Clojure代码中map操作未改变atom的值?
问题原因与解决办法
先看原代码:
(defn table-rows [data] (def rows (atom [:tr])) (map #(swap! rows conj [:td {:name %}]) data) @rows) (table-rows [{:name "foo"} {:name "bar"}])
执行后返回[:tr]的核心原因是Clojure中map的惰性求值特性:
map返回的是惰性序列,只有当你主动遍历、消费这个序列时,里面的函数(也就是swap!操作)才会被执行。原代码里只调用了map但完全没处理它的返回值,相当于这些swap!根本没跑过,atom自然不会有变化。
另外还有个明显的错误:
- 在函数内部用
def定义rows是不规范的,def会创建全局变量,每次调用函数都会覆盖这个全局绑定,应该用let来创建局部的atom。
修复方案
两种常用的修复方式:
方式一:用doall强制消费惰性序列
(defn table-rows [data] (let [rows (atom [:tr])] (doall (map #(swap! rows conj [:td {:name (:name %)}]) data)) @rows)) (table-rows [{:name "foo"} {:name "bar"}]) ;; 结果:[:tr [:td {:name "foo"}] [:td {:name "bar"}]]
doall会遍历整个map生成的惰性序列,触发所有swap!操作。
方式二:用doseq执行副作用遍历
如果不需要map返回的序列,只是要执行swap!的副作用,用doseq更合适:
(defn table-rows [data] (let [rows (atom [:tr])] (doseq [item data] (swap! rows conj [:td {:name (:name item)}])) @rows))
doseq专门用于遍历集合执行副作用,语义更清晰,也不会有惰性求值的问题。
内容的提问来源于stack exchange,提问作者THX1137
相关产品推荐
相关产品推荐

