37

好的。我一直在修补 Clojure,并且不断遇到同样的问题。让我们看一下这段代码:

(let [x 128]
  (while (> x 1)
    (do
      (println x)
      (def x (/ x 2)))))

现在我希望这会打印出一个以 128 开头的序列,如下所示:

128
64
32
16
8
4
2

相反,它是一个无限循环,一遍又一遍地打印 128。显然我预期的副作用不起作用。

那么我应该如何在这样的循环中重新定义 x 的值呢?我意识到这可能不像 Lisp(我可以使用递归的匿名函数,也许),但如果我不知道如何设置这样的变量,我会发疯的。

我的另一个猜测是使用 set!,但这给出了“无效的分配目标”,因为我不是绑定形式。

请告诉我这应该如何工作。

4

4 回答 4

50

def定义一个顶级变量,即使您在某些代码的函数或内部循环中使用它。你得到let的不是vars。根据以下文档let

用 let 创建的局部变量不是变量。一旦创造了他们的价值观就永远不会改变!

(重点不是我的。)您在这里的示例不需要可变状态;你可以使用loopand recur

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

如果你想变得花哨,你可以loop完全避免显式。

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
  (doseq [x xs] (println x)))

如果您真的想使用可变状态,原子可能会起作用。

(let [x (atom 128)]
  (while (> @x 1)
    (println @x)
    (swap! x #(/ %1 2))))

(你不需要do;while将它的主体包裹在一个明确的主体中。)如果你真的非常想用vars做到这一点,你必须做一些像这样可怕的事情。

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))

但这很丑陋,而且根本不是惯用的 Clojure。要有效地使用 Clojure,您应该尝试停止考虑可变状态。尝试以非函数式风格编写 Clojure 代码肯定会让你发疯。一段时间后,您可能会发现实际上很少需要可变变量,这令人惊喜。

于 2009-06-02T17:47:45.277 回答
13

Vars(这就是你“定义”某些东西时得到的)并不意味着要重新分配(但可以):

user=> (def k 1)
#'user/k
user=> k
1

没有什么能阻止你这样做:

user=> (def k 2)
#'user/k
user=> k
2

如果你想要一个线程本地可设置的“地方”,你可以使用“绑定”和“设置!”:

user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0

那么你可以写一个这样的循环:

user=> (binding [j 0]
         (while (< j 10)
           (println j)
           (set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil

但我认为这是非常单调的。

于 2009-06-18T04:23:17.653 回答
6

如果您认为在纯函数中具有可变局部变量将是一个很好的方便功能,并且不会造成任何伤害,因为该函数仍然是纯函数,您可能会对这个邮件列表讨论感兴趣,其中 Rich Hickey 解释了他从语言中删除它们的原因. 为什么不是可变的本地人?

相关部分:

如果局部变量是变量,即可变的,那么闭包可以关闭可变状态,并且考虑到闭包可以逃逸(没有额外的禁止),结果将是线程不安全的。人们肯定会这样做,例如基于闭包的伪对象。结果将是 Clojure 方法中的一个巨大漏洞。

如果没有可变的局部变量,人们就不得不使用 recur,一种功能性循环结构。虽然一开始这可能看起来很奇怪,但它就像带有突变的循环一样简洁,并且生成的模式可以在 Clojure 的其他地方重用,即 recur、reduce、alter、commute 等都(逻辑上)非常相似。即使我可以检测并防止变异闭包逃逸,我还是决定保持这种方式以保持一致性。即使在最小的上下文中,非变异循环也比变异循环更容易理解和调试。在任何情况下,Var 都可以在适当的时候使用。

大部分后续帖子都涉及实现with-local-vars宏;)

于 2014-10-20T17:02:47.920 回答
3

您可以更惯用地使用iterateandtake-while来代替,

user> (->> 128
           (iterate #(/ % 2))
           (take-while (partial < 1)))

(128 64 32 16 8 4 2)
user>
于 2016-07-04T22:09:10.697 回答