Programmer's Note

コード読み書きの備忘録。

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とかの感覚で書いていると、まったくよくわからない。

で、いろいろ試してみて、結局、以下のように理解した。

  1. まず、上記(for ...)では2要素のシーケンス(nil, nil)を返す。
  2. 返り値の中身がnilなのは、(for ...)の中の実行関数(print x)nilを返すから
  3. REPLの場合は、REPLの環境自体がそのシーケンスを(表示するために)使ったので、その場で評価された。
  4. 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の場合と結果が違うのが気になる。こちらは()の中に12が表示されている。 これ、ぎりぎりまでシーケンス生成((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回目はできあいのシーケンスをそのまま使うてことだな。