Ruby の Enumerator でジェネレータを作ったり、遅延評価してみる
この記事は以下のページに移転しました.
Ruby には Enumerable モジュールってのがあって、これを include したオブジェクトは、自身に対して何かしらの反復処理ができるようになる*1。
また、その反復処理を用いた Enumerable#map
とか Enumerable#select
*2 とか Enumerable#reduce
とかが使えるようになる。
更に、Enumerator というものがある。Enumerable
がモジュールなのに対して、Enumerator
はクラスなので、インスタンス化できる。また、Enumerator
は Enumerable
を include している。この Enumerator
は、外部イテレータ、いわゆるジェネレータとして使える。
フィボナッチ数列のジェネレータを作ってみよう。
fib = Enumerator.new do |y| a, b = 0, 1 loop do y << a a, b = b, a + b end end fib.next # => 0 fib.next # => 1 fib.next # => 1 fib.next # => 2 fib.next # => 3 fib.take(10) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Enumerator.new
にブロックを渡すと、ブロック引数として Enumerator::Yielder
のインスタンスが渡される。こいつの <<
メソッドの引数として値を渡してやると、ジェネレータとして値を返して(yield して)くれる。
更に、Enumerator::Lazy というものもある。こちらは、map
flat_map
select
reject
grep
take
drop
zip
などのメソッドが、即値で配列を返すのではなく、別の Enumerator::Lazy
のインスタンスを返すようにオーバーライドされている。
先程のフィボナッチ数列のジェネレータの遅延評価バージョンを作ってみる。Enumerable#lazy
を呼ぶと、Enumerator::Lazy
のインスタンスが得られる。
lazy_fib = fib.lazy sq = -> x { x**2 } lazy_fib.map(&sq).select(&:odd?).first(10) # => [1, 1, 9, 25, 169, 441, 3025, 7921, 54289, 142129] # first は eager lazy_fib.map(&sq).select(&:odd?).take(10) # => #<Enumerator::Lazy: ...> # take は lazy
遅延評価!遅延評価!🎉@ryotakameoka 遅延評価!遅延評価!🎉
— wataori (@cotton_ori) April 22, 2015