CommonLispで次のことができることを私は知っています:
CL-USER> (let ((my-list nil))
(dotimes (i 5)
(setf my-list (cons i my-list)))
my-list)
(4 3 2 1 0)
Clojureでこれを行うにはどうすればよいですか?特に、Clojureにsetfを持たずにこれを行うにはどうすればよいですか?
CommonLispであなたがしていることの私の個人的な翻訳はClojurewiseになります:
(into (list) (range 5))
その結果:
(4 3 2 1 0)
少し説明:
この関数into
は、すべての要素をコレクションに結合します。(list)
ここでは、他のコレクション(ここでは範囲)から作成された新しいリストです0 .. 4
。の動作はconj
データ構造ごとに異なります。リストの場合、は次のようにconj
動作しcons
ます。リストの先頭に要素を配置し、それを新しいリストとして返します。だから何がこれです:
(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 (list))))))
これは、CommonLispで行っていることと似ています。Clojureの違いは、1つのリストを変更するのではなく、常に新しいリストを返すことです。ミューテーションは、Clojureで本当に必要な場合にのみ使用されます。
もちろん、このリストをすぐに入手することもできますが、これはおそらくあなたが知りたかったことではありません。
(range 4 -1 -1)
また
(reverse (range 5))
または...私が思いつくことができる最短のバージョン:
'(4 3 2 1 0)
;-)。
Clojureでこれを行う方法は、それを行わないことです。Clojureは可変状態を嫌います(利用可能ですが、あらゆる小さなことに使用することはお勧めしません)。代わりに、パターンに注意してください。実際にコンピューティングを行っています(cons 4 (cons 3 (cons 2 (cons 1 (cons 0 nil)))))
。これは、リデュース(または必要に応じてフォールド)に非常によく似ています。だから(reduce (fn [acc x] (cons x acc)) nil (range 5))
、、それはあなたが探していた答えをもたらします。
Clojureは、スレッドセーフのためにローカル変数の変更を禁止していますが、変更がなくてもループを作成することは可能です。ループの実行ごとにmy-list
異なる値を設定する必要がありますが、これは再帰を使用して実現することもできます。
(let [step (fn [i my-list]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))]
(step 0 nil))
Clojureには、新しい関数、つまりを作成せずに「ループを実行する」方法もありますloop
。のように見えlet
ますが、本体の先頭にジャンプしてバインディングを更新し、。を使用して本体を再度実行することもできますrecur
。
(loop [i 0
my-list nil]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))
再帰的な末尾呼び出しを使用した「更新」パラメーターは、変数の変更と非常によく似ていますが、重要な違いが1つありmy-list
ます。Clojureコードを入力すると、その意味は常に。の値になりmy-list
ます。入れ子関数が閉じてmy-list
ループが次の反復に続く場合、入れ子関数は常にmy-list
入れ子関数が作成されたときに持っていた値を参照します。ローカル変数はいつでもその値に置き換えることができ、再帰呼び出しを行った後に持っている変数は、ある意味で別の変数です。
(Clojureコンパイラーは、この「新しい変数」に余分なスペースが必要ないように最適化を実行します。変数を記憶する必要がある場合、その値がコピーされ、recur
呼び出された場合、古い変数が再利用されます。)
このためrange
に、手動で設定した手順で使用します。
(range 4 (dec 0) -1) ; => (4 3 2 1 0)
dec
終了ステップを1で減らし、値0を取得します。
user =>(範囲5) (0 1 2 3 4) user =>(take 5(iterate inc 0)) (0 1 2 3 4) user =>([x [-1 0 123]の場合] (inc x)); 何が起こっているのかを明確にするためだけに (0 1 2 3 4)
setf
状態の突然変異です。Clojureはそれについて非常に具体的な意見を持っており、必要に応じてそのためのツールを提供します。上記の場合はそうではありません。
(let [my-list (atom ())]
(dotimes [i 5]
(reset! my-list (cons i @my-list)))
@my-list)
(def ^:dynamic my-list nil);need ^:dynamic in clojure 1.3
(binding [my-list ()]
(dotimes [i 5]
(set! my-list (cons i my-list)))
my-list)
これは私が探していたパターンです:
(loop [result [] x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
StuartHallowayとAaronBedraによるProgrammingClojure (Second Edition)で答えを見つけました。