lazy-seqでrepeat, repeatedly, cycleを書いてみる
Clojureの勉強に4clojureというサイトを使っている。
プログラミングで比較的ポピュラーな公案形式の問題集である。
よく出てくる問題の形式として、Clojureの基本関数を他の基本関数を使って実装してみる、というものがある。いい機会なのでlazy-seq
という遅延評価のシーケンスを作成する関数を使って、無限かもしれないシーケンスを作成する関数群を実装してみる。
(defn my-repeat ([x] (lazy-seq (cons x (my-repeat x)))) ([n x] (take n (my-repeat x))))
とりあえず引数が1つだけの場合は無限にその引数を返し続けるシーケンス。引数が[n x]と二つある場合は、xをn回返すシーケンス。末尾再帰でもなさそうなので(loop/recurを使っていないから)stack overflowの可能性があるかと思って
(apply + (take 100000000 (my-repeat 5)))
こんなコードを試してみたが、時間はかかっても問題なく実行できた。lazy-seqでも末尾再帰になってるのだろうか。
あとmulti-arity対応が感動的に楽。
(defn my-repeatedly ([f] (lazy-seq (cons (f) (my-repeatedly f)))) ([n f] (take n (my-repeatedly f))))
ほぼrepeat
と同じ理屈。同じ関数を何回も呼び出す、ということで乱数や副作用のある関数が対象になる。(純粋関数だったら一回呼び出して結果をrepeat
するだけでもいいので)
(defn my-cycle [col] (lazy-seq (concat col (my-cycle col))))
arityが一つに定まっている分シンプル。repeat
とrepeatedly
が一つの値や関数の戻り値の連なりだったのに対して、コレクションの内容のループなのでcons
ではなくconcat
を使っている。concat
自身もlazyなので問題なく使える。末尾再帰も行われているようだ。
次はrange
, map
, filter
やiterate
もlazy-seq
で書いてみたい。