clojure Atom 示例代码:为什么 let 和 def 之间有区别?

发布于 2025-01-17 19:11:28 字数 1431 浏览 0 评论 0 原文

我阅读了文档 https://clojure.org/reference/atoms 并尝试了有关斐波那契的代码。

根据下面的输出,在测试 3 中,使用 def 所花费的时间与预期一样短。但在测试2中,使用let而不是def重新定义fib需要相当长的时间。我想知道为什么 let 的工作方式与 def 不同?

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2)))))

(defn memorize [f]
  (let [mem (atom {})]
    (fn [& args]
      (if-let [e (find @mem args)]
        (val e)
        (let [ret (apply f args)]
          (swap! mem assoc args ret)
          ret)))))


(deftest test-memorize
  (testing "test memoize 1 - using fib"
    (println "test 1")
    (is (> (time (fib 35)) 0)))
  (testing "test memoize 2 - uising `(let [fib (memorize fib)])"
    (println "test 2")
    (let [fib (memorize fib)]
      (is (> (time (fib 35)) 0))))
  (testing "test memoize 3 - using `(def fib (memorize fib))"
    (println "test 3")
    (def fib (memorize fib))
    (is (> (time (fib 35)) 0)))

输出:

$ lein test :only app.model-test/test-memorize

lein test app.model-test
test 1
"Elapsed time: 1418.226187 msecs"
test 2
"Elapsed time: 1391.479784 msecs"
test 3
"Elapsed time: 0.215439 msecs"

Ran 1 tests containing 3 assertions.
0 failures, 0 errors.

I read the doc https://clojure.org/reference/atoms and triy the codes about Fibonacci.

According the outputs below, in test 3, it takes as short as expected, using def. But in test 2,it take quite long time, using let instead of def to redefine fib. I wonder why let does NOT work as the same way as def?

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2)))))

(defn memorize [f]
  (let [mem (atom {})]
    (fn [& args]
      (if-let [e (find @mem args)]
        (val e)
        (let [ret (apply f args)]
          (swap! mem assoc args ret)
          ret)))))


(deftest test-memorize
  (testing "test memoize 1 - using fib"
    (println "test 1")
    (is (> (time (fib 35)) 0)))
  (testing "test memoize 2 - uising `(let [fib (memorize fib)])"
    (println "test 2")
    (let [fib (memorize fib)]
      (is (> (time (fib 35)) 0))))
  (testing "test memoize 3 - using `(def fib (memorize fib))"
    (println "test 3")
    (def fib (memorize fib))
    (is (> (time (fib 35)) 0)))

The outputs:

$ lein test :only app.model-test/test-memorize

lein test app.model-test
test 1
"Elapsed time: 1418.226187 msecs"
test 2
"Elapsed time: 1391.479784 msecs"
test 3
"Elapsed time: 0.215439 msecs"

Ran 1 tests containing 3 assertions.
0 failures, 0 errors.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡看悲欢离合 2025-01-24 19:11:28

...为什么让我们不像DEF那样工作? ... 因为它们的目的不同。


    (testing "test memoize 3 - using `(def fib (memorize fib))"
       (println "test 3")
       (def fib (memorize fib))
       (is (> (time (fib 35)) 0))) 

正在重新定义 global fib 的值是返回的值,请记住fib 。当用(FIB 35)调用记忆的代码将调用以前命名 fib 。原始,fib呼叫,按名称, fib 现在是记忆的版本,用于计算fib 34。fib 34调用的计算 fib fib 计算FIB 33因此。 FIB的递归实现创建了一棵计算树。通过重新定义 fib 在顶部注入记忆工作,第一个调用(fib 35)是将树修剪为一个分支。

对于,让做同样的事情将需要Clojure使用动态范围而不是词汇范围。


观察 fib 的一些方法正在顶部重新定义。运行测试后,(DOC FIB),请看到您的文档字符串不存在。因为 fib 不再定义为您编写的函数。

交换LET和DEF测试的顺序。然后,将获得回忆的FIB。

如果您使用 fn 来定义FIB,则

(def fib (fn fib [n] 
     (if (<= n 1) 
         n (+ (fib (dec n))
         (fib (- n 2))))))

在其中呼叫fib的调用将始终转向自身,因为它正在使用 fn < fib fn < fib /代码>表格。

... why let does NOT work as the same way as def? ... Because they serve different purposes.


    (testing "test memoize 3 - using `(def fib (memorize fib))"
       (println "test 3")
       (def fib (memorize fib))
       (is (> (time (fib 35)) 0))) 

Is redefining the global value of fib to be the value returned by memorize fib. When the called with (fib 35) the memorize code will call what used to be named fib. That, original, fib calls, by name, fib which is now the memoized version, of fib to calculate fib 34. The calculation of fib 34 calls fib to calculate fib 33. So when (fib 34) returns and (fib 35) calls (fib 33), but that value is cached already. The recursive implementation of fib creates a tree of computation. By redefining fib at the top to inject memoization the work, on the first call to (fib 35) is trimming the tree to a single branch.

For let to do the same thing would require Clojure to use dynamic scope instead of lexical scope.


Some ways to observe that fib is being redefined at the top. After running you tests, (doc fib) and see that your documentation string is not there. Because fib is no longer defined as the function you wrote.

Swap the order of the let and def tests. Then let will be getting the memoized fib.

If you define fib using fn as such:

(def fib (fn fib [n] 
     (if (<= n 1) 
         n (+ (fib (dec n))
         (fib (- n 2))))))

The call to fib inside will always go to itself, because it is using the local binding for fib from the fn form.

滥情哥ㄟ 2025-01-24 19:11:28

正如香农·塞弗伦斯已经回答的那样。 def 创建一个全局变量。 let 创建本地绑定。

哪种效果对您的函数有这种差异?

情况 2 (let):

您创建一个新函数(fib 的记忆版本)并将其在本地绑定到名称 fib。然后你调用fibfib 递归调用 fib,但在 defn fib ... 内部,fib 指的是全局定义的 fib,它不会被记忆。所以实际上,没有缓存。

(testing "test memoize 2 - uising `(let [fib (memorize fib)])"
  (println "test 2")
  (let [fib (memorize fib)] ;-> new memoized version of fib, which is bound to the name fib
    (is (> (time (fib 35)) 0)))); but it is only available inside the let block

defn fib 的影响:

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2))))) ;-> calls fib, but fib refers to the globally defined fib, which is not memoized

情况 3 (def):

您创建 fib 的记忆版本,然后全局重新声明 fib 的值是新的记忆版本。您调用fibFib 调用自身,但现在它指的是新定义的版本,该版本已被记忆。

(def fib (memorize fib)) ; -> you create a new global fn fib, which is memoized
(is (> (time (fib 35)) 0))

对 defn fib 的影响:

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2))))) ; -> now, the memoized version of fib is called

As Shannon Severance already answered. def creates a global var. let creates a local binding.

Which effect has this difference on your function?

Case 2 (let):

You create a new function (a memoized version of fib) and bind it locally to the name fib. Then you call fib. fib calls fib recursively, but inside defn fib ... fib refers to the globally defined fib, which is not memoized. So effectively, there is no caching.

(testing "test memoize 2 - uising `(let [fib (memorize fib)])"
  (println "test 2")
  (let [fib (memorize fib)] ;-> new memoized version of fib, which is bound to the name fib
    (is (> (time (fib 35)) 0)))); but it is only available inside the let block

Effect on defn fib:

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2))))) ;-> calls fib, but fib refers to the globally defined fib, which is not memoized

Case 3 (def):

You create a memoized version of fib, then you globally redeclare fib's value to be the new memoized version. You call fib. Fib calls itself, but now it refers to the new defined version, which is memoized.

(def fib (memorize fib)) ; -> you create a new global fn fib, which is memoized
(is (> (time (fib 35)) 0))

Effect on defn fib:

(defn fib "original fib function" [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2))))) ; -> now, the memoized version of fib is called
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文