2009-06-02 49 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>