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惰性的粗略近似,但大致上是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' 是懒惰的,好吧
第一次打印会找到 @cnt,因此懒惰序列没有被求值
第二次打印会去寻找 cnt',懒惰序列会被求值并且 @cnt 被更新
第三次打印只会显示更新的 @cnt

更新 @cnt 是求值(可能是打印或写入文件)的副作用
我认为 Clojure 在这里做对了
...