はじめに
まず簡単な例を挙げてみます。
例えば、1から順番に整数の二乗した値を足し算していき、累積的に加算された値が10000を超えない最大の数を求める場合です。
Haskellで書くとこんな感じです。無限リストが使えます。
last $ takeWhile (<=10000) $ scanl (\b a-> b+a*a) 0 [1..]
Rubyでは
Rubyのinjectやeach_with_objectが近いのですが、遅延評価してくれないので無限配列を使えません。近い機能をもつメソッドを作ってみました。
class Enumerator::Lazy
def scan(init)
prev=init
Enumerator::Lazy.new(self){|yielder, *values|
yielder << (prev=yield prev, *values)
}
end
end
puts (1..Float::INFINITY).lazy.scan(0){|r,a| r+a*a}.take_while{|a| a<=10000}.to_a.last
Haskellのscanlでは、初期値がリストの先頭に入りますが、上のruby版scanは初期値が入らない違いがあります。
他の使いかたも
初期値に空の配列を与えて累積的に配列に要素を追加していくことで、いろいろな計算で遅延評価できるようになります。
uniqの遅延評価だと
p [1,2,3,4,1,3].lazy.scan([]){|r,a| r.include?(a) ? r : r+[a]}.to_a.last
少し危険な香りがしますが、破壊的代入で
p [1,2,3,4,1,3].lazy.scan([]){|r,a| r.include?(a) ? r : r<<a}.to_a.last
もありかなあ、もっとも遅延評価できるuniqが標準で入っていますのでわざわざ作る必要ないのですが
そこまでに計算した累積結果を元に計算できますので、いろいろな応用がありそうです。