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

MBC*_*ook 36 function clojure let

好.我一直在修补Clojure,我不断遇到同样的问题.我们来看看这段代码:

(let [x 128]
  (while (> x 1)
    (do
      (println x)
      (def x (/ x 2)))))
Run Code Online (Sandbox Code Playgroud)

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

128
64
32
16
8
4
2
Run Code Online (Sandbox Code Playgroud)

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

那么我该如何在这样的循环中重新定义x的值呢?我意识到这可能不像Lisp那样(我可能会使用一个匿名函数来对它进行自我修复),但是如果我不知道如何设置这样的变量,我会发疯的.

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

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

Bri*_*per 50

def定义一个toplevel var,即使你在一些代码的函数或内部循环中使用它.你得到let的不是变种.根据以下文档let:

使用let创建的本地不是变量.一旦创建,他们的价

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

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))
Run Code Online (Sandbox Code Playgroud)

如果你想要想要你可以loop完全避免明确.

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

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

(let [x (atom 128)]
  (while (> @x 1)
    (println @x)
    (swap! x #(/ %1 2))))
Run Code Online (Sandbox Code Playgroud)

(你不需要一个do; while为你准备一个明确的身体.)如果你真的,真的想用vars这样做你必须做一些像这样可怕的事情.

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))
Run Code Online (Sandbox Code Playgroud)

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

  • 副作用是Lispy,取决于你正在看哪个Lisp.在Common Lisp中你会逃避(循环为x = 128然后(/ x 2)而(> x 1)do(print x)).但副作用不是Clojurish. (4认同)

Chr*_*ris 13

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

user=> (def k 1)
#'user/k
user=> k
1
Run Code Online (Sandbox Code Playgroud)

没有什么可以阻止你这样做:

user=> (def k 2)
#'user/k
user=> k
2
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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


use*_*610 6

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

相关部分:

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

没有可变的本地人,人们被迫使用recur,一个功能性的循环结构.虽然这一开始可能看起来很奇怪,但它与带有变异的循环一样简洁,并且得到的模式可以在Clojure的其他地方重复使用,即重复,减少,改变,通勤等都是(逻辑上)非常相似的.即使我可以检测并防止变异闭包逃逸,我决定保持这种方式以保持一致性.即使在最小的上下文中,非变异循环也比变异循环更容易理解和调试.在任何情况下,Vars都可以在适当的时候使用.

大多数后续职位涉及实施with-local-vars宏观;)