2024 Clojure 状态调查! 中分享您的想法。

欢迎!请查阅 关于 页面以了解更多关于此功能的信息。

0
语法和reader

您好,

(def cnt (atom 0))
(defn up[] (swap! cnt #(inc %)))
(defn down[] (swap! cnt #(dec %)))
(defn shift-by[offset] (swap! cnt #(+ % offset)))
(dotimes [_ 10] (up))
(println "cnt up => " @cnt)
(loop [i 0] (if (< i 10) (do (down)(recur (inc i)))))
(println "cnt down => " @cnt)
(def cnt' (for[i (range 5)] (shift-by i)))
(println "cnt shift1 => " @cnt)
(println "cnt' => " cnt')
(println "cnt shift2 => " @cnt)

cnt up => 10
cnt down => 0
cnt shift1 => 0
cnt' => (0 1 3 6 10)
cnt shift2 => 10

为什么 shift1 显示为 0,而 shift2 显示为 10?

此致,
Daniel

1 个回答

0

我第一反应是 for 的惰性。试试 (def cnt' (vec (for[i (range 5)] (shift-by i)))) 看看这会改变什么。

是的,这确实有所改变。

(doall ...) 也可以。

我认为我原本期望使用 atom 将会强制惰性表达式进行评估。
by
打印序列 (cnt') 是实现由 for 产生的惰性序列。
by
>>>>打印序列 (cnt') 是实现由 for 产生的惰性序列。

是的,那么为什么 Clojure 不等待 cnt' 进行求值并打印 0 呢?
by
因为 `for` 返回(求值后)为惰性序列,这意味着它只会随着单个元素的消费而实现(大致相似但不完全相同,这就是 Clojure 惰性的大致原理)。实现惰性序列的其中一个原因正是整个(可能无限的)序列不需要被实现,因此产生惰性序列的操作(如 for)可以快速返回。

所以 cnt' 的值是一个惰性序列。当你打印它时,打印器会看到它是一个序列,然后它会迭代其元素,这将导致元素被实现,从而触发实现元素时代码中的副作用。

vec 产生一个非惰性向量,所以需要 cnt' 中的所有元素,这也将导致它们被实现。doall 专门用于实现所有元素的目的。

回答“为什么 Clojure 不等待 cnt' 进行求值”这个问题——它会等待,但如我上所述,cnt' 求值后为惰性序列,这并不保证其任何元素都已经实现。

我认为惰性效果是一种不良实践https://stuartsierra.com/2015/08/25/clojure-donts-lazy_effects
by
我想我现在明白这里发生了什么

(def cnt (atom 0))
(def cnt' (for[i (range 5)] (shift-by i)))
(println "cnt shift1 => " @cnt)
(println "cnt' => " cnt')
(println "cnt shift2 => " @cnt)

cnt' 是懒惰的,好吧
第一次打印会去取 @cnt,因此懒惰序列没有被评估
第二次打印会去取 cnt',懒惰序列被评估,@cnt 被更新
第三次打印只显示更新的 @cnt

更新 @cnt 是评估的结果之一(可能是打印或写入文件)
我认为 Clojure 在这里做得正确
...