Atom 和闭包在Clojure中

huangapple go评论61阅读模式
英文:

Atom and closure in Clojure

问题

我尝试学习 https://clojure.org/reference/atoms 中的 memoize 示例时,我感到困惑,不明白为什么 mem 变量在多次函数调用间保持相同的原子对象。

根据 https://stackoverflow.com/questions/14874970/clojure-closure 中的问答,这是闭包的一种行为。然而,由于下面两个函数的行为不同,我开始认为这是原子的一种行为。你能解释一下以下两个函数的差异,从闭包和原子的视角来看吗?

(def counter
  (let [count (atom 0)]
      (fn [] (do (swap! count inc)
                 @count))))

(println (counter)) ;; 1
(println (counter)) ;; 2

(def counter2
  (let [count {:value 0}]
      (fn () (do (assoc count :value (inc (get count :value)))
                 (get count :value)))))

(println (counter2)) ;; 0
(println (counter2)) ;; 0

(编辑)
阅读 Eugene 的回答后,我用以下函数替换了第二个示例函数。计数器增加其值的原因是计数器是一个闭包,闭包对可变值进行了封闭。

(def counter2
  (let [count (int-array '(0))]
      (fn [] (do (aset count 0 (inc (get count 0)))
                 (get count 0))))) ;; 此行非必需,但为了与上面的函数保持一致而添加。

(println (counter2)) ;; 1
(println (counter2)) ;; 2
英文:

When I tried to learn memoize example in https://clojure.org/reference/atoms , I became confused why mem variable holds the same atom object across multiple function calls.

According to the QA in https://stackoverflow.com/questions/14874970/clojure-closure , this is a behavior of closure. However as the following two functions behave differntly, I come to think that this is a behavior of atom. Could you explain the difference of the following two functions from the view of closure and atom?

(def counter
  (let [count (atom 0)]
      (fn [] (do (swap! count inc)
                 @count))))

(println (counter)) ;; 1
(println (counter)) ;; 2

(def counter2
  (let [count {:value 0}]
      (fn [] (do (assoc count :value (inc (get count :value)))
                  (get count :value)))))

(println (counter2)) ;; 0
(println (counter2)) ;; 0

(EDIT)
After reading Eugene's answer I replace the second example function with the following function. The reason why the counter increases its value is that the counter is a closure being closed over a variable with mutable value.

(def counter2
  (let [count (int-array '(0))]
      (fn [] (do (aset count 0 (inc (get count 0)))
                 (get count 0))))) ;; This line is not necessary but is added to keep consistency with the functions above.

(println (counter2)) ;; 1
(println (counter2)) ;; 2

答案1

得分: 4

Both functions are closed over a single value. The first function is closed over a container whose value can be changed. The second function is closed over an immutable map that cannot be changed.

swap! changes the value inside an atom, in-place. (BTW don't mix atom operations like you did with swap! followed by @ unless you're absolutely certain it must be done since it makes the code not atomic. In this case, just return the result of swap!.)

assoc creates a new immutable map and returns it, without affecting its arguments.

英文:

Both functions are closed over a single value. The first function is closed over a container whose value can be changed. The second function is closed over an immutable map that cannot be changed.

swap! changes the value inside an atom, in-place. (BTW don't mix atom operations like you did with swap! followed by @ unless you're absolutely certain it must be done since it makes the code not atomic. In this case, just return the result of swap!.)

assoc create a new immutable map and returns not, without affecting its arguments.

答案2

得分: 1

The new example after editing uses a Java array, which is mutable. Aset can be used to mutate a Java array, aset documentation. Thus you are changing the value in an actual memory location, instead of getting a new object as you would with an immutable data structure.

You lose atomicity and thread safety when you start using a Java int array like this. So the second example is a bit of an anti-pattern.

Clojure data structures are immutable, but Clojure has a lot of interop functionality to work with Java, as it runs on the JVM.

英文:

The new example after editing uses a Java array, which is mutable. Aset can be used to mutate a Java array, aset documentation. Thus you are changing the value in an actual memory location, instead of getting a new object as you would with an immutable data structure.

You lose atomicity and thread safety when you start using a Java int array like this. So the second example is a bit of an anti-pattern.

Clojure data structures are immutable, but Clojure has a lot of interop functionality to work with Java, as it runs on the JVM.

huangapple
  • 本文由 发表于 2023年5月21日 22:53:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76300494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定