Ruby2.0 で追加されると噂の Enumerable#lazy は、Ruby で遅延リストが使えるようになる、個人的に待望の機能。map や select をメソッドチェーンしたとき、何度もループをぶん回すのが不満だったからなぁ。
Enumerable#lazy は以前ちらっとソースコードを見たんだけど、確か Enumerator を使って実装していた。Enumerator はソースコード見たこと無いけど、Fiber を使って実装したらしい。Fiber 使ったこと無いな、そういえば。
Fiber の勉強として、Enumerable#lazy を再発明してみた。あくまで勉強なので、Enumerator は使わず、Enumerator もどきを Fiber を使って自作している。
# 自作 Enumerator class MyEnumerator include Enumerable # each を終了させるための例外 class StopIteration < Exception; end # Fiber をラップして使いやすくする class Yielder def initialize(&block) @fiber = Fiber.new do block.call(self) raise StopIteration end end def <<(value) Fiber.yield(value) end def next @fiber.resume end end def initialize(collection=[], method=:each, &block) if block_given? @block = block else @block = Proc.new do |yielder| collection.send(method) do |args| yielder << args end end end @yielder = Yielder.new(&@block) rescue StopIteration end def next @yielder.next end # StopIteration が発生するまで値を取り出し続ける def each(&block) loop do block.call(self.next) end rescue StopIteration end end module Enumerable # 自作の Lazy クラス class MyLazy < MyEnumerator def initialize(collection, &block) super(collection, :each, &block) end # 遅延評価バージョンの map def map(&block) MyLazy.new(self) do |yielder| self.each do |n| yielder << block.call(n) end end end # 遅延評価バージョンの select def select(&block) MyLazy.new(self) do |yielder| self.each do |n| if block.call(n) yielder << n end end end end end # コレクションを MyLazy に変換 def my_lazy MyLazy.new(self) end end # テスト (1..10).my_lazy.map { |n| puts "map:#{n}" n * n }.select { |n| puts "select:#{n}" n % 2 == 0 }.each { |n| puts "each:#{n}" }
実行結果
map:1 select:1 map:2 select:4 each:4 map:3 select:9 map:4 select:16 each:16 map:5 select:25 map:6 select:36 each:36 map:7 select:49 map:8 select:64 each:64 map:9 select:81 map:10 select:100 each:100