2009-06-02 46 views
35

好的。我一直在修补Clojure,并且不断遇到同样的问题。让我们的代码,这个小片段:在Clojure循环中重新定义let'd变量

(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 !,但是它给出了“无效的赋值目标”,因为我没有绑定的形式。

请启发我如何这应该工作。

回答

48

def定义了一个toplevel var,即使您在函数或某个代码的内部循环中使用它。你在let得到的不是变数。每the documentation for 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包裹它的身体在一个明确的一个给你。)如果你真的,真的想和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代码。过了一段时间,你可能会发现它很令人惊喜,很少有你真的需要可变变量。

+1

感谢。我意识到我的方式不是Lispy,因为副作用令人不悦。我正在通过一些东西(一个欧拉项目问题)进行黑客攻击,无法让这个简单的测试案例起作用,证明我没有理解某些东西。谢谢您的帮助。我忘了循环可以保持复发,工作很干净(没有额外的功能做递归)。 – MBCook 2009-06-02 18:25:35

12

瓦尔(这是你当你“高清”的东西)是不是要重新分配(但可以):

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

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

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

如果你想有一个线程本地设定的“地点”,您可以使用“绑定”和“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 

,那么你可以WR伊特一个循环是这样的:

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

但我认为这是相当unidiomatic。

5

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

相关部分:

如果当地人的变量,也就是可变的,则关闭可以关闭了 可变状态,并且,考虑到封竟能(不包括一些额外的 禁止相同),结果会是线程不安全的。而且人们 肯定会这样做,例如,基于闭包的伪对象。结果 将是Clojure方法中的一个巨大漏洞。

没有可变的当地人,人们被迫使用循环,功能 循环构造。尽管起初这看起来很奇怪,但它恰如简化为具有突变的环,并且所得模式可以在Clojure中的其他地方重复使用,即,复发,减少,改变,通勤等 都是(逻辑上)非常相似。尽管我可以检测并阻止变异关闭逃脱,但我决定保持它的这种方式 的一致性。即使在最小的环境中,非变异循环比变异变异更易于理解和调试。在任何情况下,Vars 适用时均可使用。

大部分实施with-local-vars宏随后的帖子关注;)

1

你可以更地道使用iteratetake-while而是

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

(128 64 32 16 8 4 2) 
user>