在Clojure循环中重新定义let'd变量

时间:2009-06-02 17:05:44

标签: clojure let function

行。我一直在修补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 个答案:

答案 0 :(得分:50)

def定义了一个toplevel var,即使你在某些代码的函数或内部循环中使用它也是如此。你在let得到的不是变种。每the documentation for let

  

使用let创建的本地不是变量。一旦创建,他们的价值观永远不会改变!

(强调不是我的。)你不需要这个例子的可变状态;您可以使用looprecur

(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)))

如果确实想要使用可变状态,则atom可能会有效。

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

(你不需要do; while为你明确包装它的身体。)如果你真的,那么想用{做} {3}}你必须做这样可怕的事情。

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

但这非常难看,而且根本不是惯用的Clojure。要有效地使用Clojure,你应该尝试停止在可变状态方面进行思考。它肯定会让你疯狂地试图以非功能性的方式编写Clojure代码。过了一段时间,你会发现很少有人真正需要可变变量,这是一个令人惊喜的事。

答案 1 :(得分:13)

Vars(当你“def”某事时所得到的)并不意味着被重新分配(但可以):

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

但我认为这非常不合时宜。

答案 2 :(得分:6)

如果您认为在纯函数中使用可变局部变量将是一个不会造成伤害的便利功能,因为该函数仍然是纯粹的,您可能会对此邮件列表讨论感兴趣,其中Rich Hickey解释了他删除它们的原因从语言。 Why not mutable locals?

相关部分:

  

如果locals是变量,即可变,那么闭包可以关闭   可变状态,并且,封闭可以逃脱(没有一些额外的   禁止相同),结果将是线程不安全的。和人   肯定会这样做,例如基于闭包的伪对象。结果   将是Clojure方法的一个巨大漏洞。

     

如果没有可变的本地人,人们就会被迫使用复发功能   循环结构。虽然这起初可能看起来很奇怪,但同样如此   简洁为具有突变的环,并且得到的模式可以是   在Clojure的其他地方重复使用,即复发,减少,改变,通勤等   都(逻辑上)非常相似。即使我能检测到并且   防止变异闭包逃脱,我决定保持这种方式   为了一致性。即使在最小的环境中,非变异循环也是如此   比变异更容易理解和调试。无论如何,Vars   可在适当时使用。

大多数后续帖子都涉及实施with-local-vars宏;)

答案 3 :(得分:2)

您可以更惯用地使用iteratetake-while

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

(128 64 32 16 8 4 2)
user>