Clojureメモ: 遅延評価
さて、(for ...)
はシーケンスを生成して返すが、これは遅延評価されるので、実際に使用されるまではシーケンスは生成されない。
REPL環境だと入力した式がその場で評価されるので、これが遅延評価されるものとは気づかない。
user=> (for [x (range 1 3)] (println x)) 1 2 (nil nil)
実際に、lein new app sample
とかでテンプレートつくって、以下のようなソースを書いてみる。
(ns sample.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (for [x (range 1 3)] (println x)))
実行してみると
sample $lein run sample $
、、、なにも表示されない?
CとかRubyとかの感覚で書いていると、まったくよくわからない。
で、いろいろ試してみて、結局、以下のように理解した。
- まず、上記
(for ...)
では2要素のシーケンス(nil, nil)
を返す。 - 返り値の中身がnilなのは、
(for ...)
の中の実行関数(print x)
がnilを返すから - REPLの場合は、REPLの環境自体がそのシーケンスを(表示するために)使ったので、その場で評価された。
lein run
で実行した場合は、プログラムのなかで(for ...)
の返り値のシーケンスを使うコードがどこにもないので、評価されずに終わった
ということか。
プログラムを下記のように書き直す。
(ns sample.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (println (for [x (range 1 3)] (println x))))
実行結果:
sample $lein run (1 2 nil nil)
微妙にREPLの場合と結果が違うのが気になる。こちらは()
の中に1
、2
が表示されている。
これ、ぎりぎりまでシーケンス生成((for..)
の中の実行)を遅らせているのだな、と理解した。
遅延評価は一回しかやらないようで、例えば、
(ns sample.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (def a (for [x (range 1 3)] (println x))) (println a) (println a))
の実行結果は、
sample $lein run (1 2 nil nil) (nil nil)
になる。
2回目はできあいのシーケンスをそのまま使うてことだな。