37

わかった。私は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

4 に答える 4

50

def関数または一部のコードの内部ループで使用する場合でも、トップレベルの var を定義します。あなたが得るletものは変数ではありません。のドキュメントにletよると:

let で作成されたローカルは変数ではありません。一度作られた価値は決して変わりません!

(強調は私のものではありません。)ここでの例では、可変状態は必要ありません。loopとを使用できますrecur

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

本当に変更可能な状態を使用したい場合は、アトムが機能する可能性があります。

(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 コードを書こうとすると、間違いなく頭がおかしくなるでしょう。しばらくすると、変更可能な変数が実際に必要になることはめったにないことに、うれしい驚きを覚えるかもしれません。

于 2009-06-02T17:47:45.277 に答える
13

Vars(何かを「定義」したときに得られるもの)は、再割り当てすることを意図していません(ただし、再割り当てすることはできます):

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

しかし、これはかなり単調だと思います。

于 2009-06-18T04:23:17.653 に答える
6

純粋な関数に変更可能なローカル変数を持たせることは害のない便利な機能であると思われる場合は、Rich Hickey が言語からそれらを削除する理由を説明しているこのメーリング リストのディスカッションに興味があるかもしれません。 . 変更可能なローカルではないのはなぜですか?

関連部分:

ローカル変数が変数、つまりミュータブルである場合、クロージャーはミュータブルな状態で閉じることができ、クロージャーが (同じものを特別に禁止することなく) エスケープできるとすれば、結果はスレッドセーフではなくなります。そして、クロージャーベースの疑似オブジェクトなど、人々は確かにそうするでしょう。その結果、Clojure のアプローチに大きな穴が開くことになります。

ミュータブル ローカルがなければ、機能的なループ構造である recur を使用せざるを得なくなります。これは最初は奇妙に思えるかもしれませんが、突然変異を伴うループと同じくらい簡潔であり、結果のパターンは Clojure の他の場所で再利用できます。つまり、recur、reduce、alter、commute などはすべて (論理的に) 非常に似ています。変異クロージャのエスケープを検出して防ぐことはできましたが、一貫性を保つためにこの方法を維持することにしました。最小のコンテキストであっても、変化しないループは、変化するループよりも理解しやすく、デバッグしやすいです。いずれにせよ、Var は適切なときに使用できます。

with-local-varsその後の投稿の大部分は、マクロの実装に関するものです ;)

于 2014-10-20T17:02:47.920 に答える