代码之家  ›  专栏  ›  技术社区  ›  MBCook

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

  •  36
  • MBCook  · 技术社区  · 15 年前

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

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

    128
    64
    32
    16
    8
    4
    2
    

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

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

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

    请告诉我这是怎么回事。

    4 回复  |  直到 15 年前
        1
  •  50
  •   slang dbr    8 年前

    def 定义顶级变量,即使在函数或某些代码的内部循环中使用它。你得到了什么 let 他们不是VAR。每 the documentation for let :

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

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

    如果你想变得花哨,你可以避免直言不讳 完全

    (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 将它的身体包裹在一个显式的身体中。)如果 真的,真的 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代码肯定会让你发疯。过一段时间后,你可能会发现你实际上很少需要可变变量,这是一件令人惊喜的事情。

        2
  •  13
  •   Chris    15 年前

    VAR(这是当您“定义”某个内容时所得到的)并不意味着要重新分配(但可以):

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

    没有什么能阻止你做:

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

    如果您想要一个线程本地可设置的“place”,可以使用“binding”和“set!”:

    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
    

    但我认为这很不符合实际。

        3
  •  6
  •   user7610    10 年前

    如果您认为在纯函数中包含可变局部变量是一个很好的、方便的特性,而且不会造成任何伤害,因为函数仍然保持纯,那么您可能会对邮件列表中的讨论感兴趣,其中Rich Hickey解释了将其从语言中删除的原因。 Why not mutable locals?

    相关部分:

    可变状态,并且,假设闭包可以转义(不需要额外的 禁止相同),其结果将是线程不安全。和人 这将是Clojure方法中的一个巨大漏洞。

    循环构造。虽然这一点一开始看起来很奇怪,但实际上也一样 简洁如带突变的循环,由此产生的模式可以 在Clojure的其他地方重复使用,即重现、减少、改变、通勤等 都(逻辑上)非常相似。即使我能探测到 为了防止变异闭包逃逸,我决定保持这种方式 为了一致性。即使在最小的上下文中,非变异循环也很重要 比变异的更容易理解和调试。无论如何,Vars 可在适当的时候使用。

    随后的大多数员额涉及执行一项战略 with-local-vars 宏;)

        4
  •  3
  •   ealfonso    8 年前

    你可以更习惯地使用 iterate take-while 相反

    user> (->> 128
               (iterate #(/ % 2))
               (take-while (partial < 1)))
    
    (128 64 32 16 8 4 2)
    user>