lazy-seqでrangeを書いてみる
range
関数はPythonでもおなじみで、引数の扱いもほぼ同じ。ただし、引数なしで0から始まって無限に続く整数のシーケンスになるというのはPythonではitertools.count
の挙動でrange
ではできない。
Clojureのlazy-seq
で書くにあたって、実は「遅延評価で無限に続く」という部分は非常に簡単。むしろ面倒くさいのは1個以上引数があった場合の挙動の違いである。つまりmulti-arity対応。
引数が一つだけの場合と複数の場合とで、第一引数の意味が変わるのが一番面倒くさい。repeat
などと同じようにまずは引数無しのものを書いてからdrop
やtake
を使って他のarityに対応しようかと思ったのだが、引数一つの場合意味合いがstartではなくendになるのでそれもおかしくなる。
ということで、引数三つの場合をベースに、他のarityはデフォルト値を入れることで対応する。
(defn my-range ([] (my-range 0 Float/POSITIVE_INFINITY 1)) ([end] (my-range 0 end 1)) ([start end] (my-range start end 1)) ([start end step] (if (or (and (pos? step) (>= start end)) (and (neg? step) (>= end start)) () (lazy-seq (cons start (my-range (+ start step) end step))))))
引数無しの場合、end
をFloat/POSITIVE_INFINITY
にすることで無限に続いても大きさ比較が成り立つようにする。
あと少しややこしいのはstep
が負の値をとる場合の対応で、これを忘れると(range 10 0 -1)
などに空のリストを返してしまう。
全体を見渡すと、やはりコアのロジックが(lazy-seq (cons start (my-range (+ start step) end step))))))
で記述できてしまうのがClojureらしくていいのではないだろうか。