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

欢迎!请参阅关于页面获取更多有关本网站如何运作的信息。

0
语法和读取器

你好,

(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 ...) 也会起到作用

我想我预期的是使用原子将迫使惰性表达式评估。
打印序列(cnt')就是实现了for产生的惰性序列。
>>> 打印序列(cnt')就是实现了for产生的惰性序列。

是的,那么为什么Clojure不等待cnt'求值而直接打印0呢?
因为`for`返回(求值)一个惰性序列,这意味着它将只在消费时才实现单个元素——这是一个粗略的近似,但大致也就是Clojure的惰性所在。实行惰性的原因之一正是整个(可能是无限的)序列不必实现——所以能够产生惰性序列的操作(如for)可以快速返回。

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

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

回答“为什么Clojure不等待cnt'求值”——它确实等待,但我上面写的,cnt'求值得到一个惰性序列,这并不保证其任何元素都已实现。

我认为惰性副作用是一种不好的实践 https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects
我想我现在明白这里发生的事情了

(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'是惰性的,好的
第一次打印到达for @cnt,因此惰性序列不会被求值
第二次输出针对cnt',并评估懒惰序列,然后更新@cnt
第三次输出仅显示更新的@cnt

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