7. Enumerator の復習 6
Enumerator ■ Object#enum_for による Enumerator の生成
Enumerable なオブジェクト enum = "abc".enum_for(:each_byte)
enum.each do |byte|
を簡単に作れる puts byte
~Ruby 1.8.6 の標準添付ライブラリ end
Ruby 1.8.7 の組込ライブラリ
■ Enumerator.new による Enumerator の生成
Enumerable::Enumerator
Ruby 1.8.8~ の組込ライブラリ str = "abc"
enum = Enumerator.new(str, :each_byte)
Enumerator
enum.each do |byte|
Ruby 1.9 で Generator の機能を puts byte
取り込み強化される end
■ Ruby 1.8 の添付ライブラリ Generator ■ Enumerator.new (Ruby 1.9 feature )
g = Generator.new do |yielder| enum = Enumerator.new do |yielder|
"abc".each_byte do |byte| "abc".each_byte do |byte|
yielder.yield byte yielder << byte
end end
end end
# callcc だから遅い # Fiber を使っているから速い
g.each do |byte| enum.each do |byte|
puts byte puts byte
end end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
8. Enumerator と Fiber 7
Enumerator は Fiber を活用するデザインパターンの1つ
Fiber における難しい概念を隠ぺい
Fiber.yield => Enumerator::Yielder#<< 、 Enumerator#each による列挙
Enumerator を使いこなせれば、十分 Fiber のメリットを活用できる
《参考: Fiber にできて、Enumerator でできないこと》
親 Fiber から 子Fiber へのデータの送付(Fiber.yield の返り値の利用)
■ Enumerator の例(フィボナッチ数) ■ 《参考》 Fiberの例(フィボナッチ数)
fib = Enumerator.new do |yielder| fib = Fiber.new do
a = b = 1 a = b = 1
loop do loop do
Fiber.yield a
yielder << a
a, b = b, a + b
a, b = b, a + b end
end end
end
def fib.each
fib.each do |i| loop do これが必要。
Enumerator の
break if i > 1000 内部実装まで
yield fib.resume だけど、これが
end
puts i
考えなくていい end 難しい。(汗)
end
fib.each do |i|
break if i > 1000
puts i
end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
9. Enumerator はベンリ 8
Enumerator なら Enumerable の便利メソッドを使える。
ブロック付メソッド呼び出しは単純なイテレーションならいいけど。。。
Enumerator はオブジェクトなので、使い回しが簡単。
引数にしたり、返り値にしたり、インスタンス変数に格納したり
Enumerator オブジェクトの生成も慣れれば簡単。
■ Enumerator の例(フィボナッチ数) ■ 《参考》 ブロック付メソッド呼び出しの例
fib = Enumerator.new do |yielder| def fibonacci
a = b = 1 a = b = 1
loop do loop do
yielder << a 慣れれば、 yield a
a, b = b, a + b カンタン a, b = b, a + b
end end
end end
fib.each do |i| fibonacci do |i|
break if i > 1000 break if i > 1000
puts i 単純なイテレーション
end puts i ならいいけど・・・。
end
fib.each_cons(2) do |i, j| Enumerable
break if i > 1000 prev = nil
の便利メソッド fibonacci do |i|
puts "#{i} #{j}" を使い放題
end break if i > 1000 面倒 ! !
puts "#{prev} #{i}" if prev
prev = i
end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
11. Enumerable#lazy の実装 10
Enumerable#lazy を使うと、Enumerable のメソッドの返り値が
Enumerator::Lazy オブジェクトになる
Enumerable モジュールに増やすメソッドは Enumerable#lazy だけ
module Enumerable def select(&block)
def lazy Lazy.new(self){|yielder, val|
Enumerator::Lazy.new(self) if block.call(val)
end yielder << val
end end
}
class Enumerator end
class Lazy < Enumerator
def initialize(obj, &block)
def take(n)
super(){|yielder|
taken = 0
begin
Lazy.new(self){|yielder, val|
obj.each{|x|
if block if taken < n
block.call(yielder, x) yielder << val
else taken += 1
yielder << x else
end raise StopIteration
} end
rescue StopIteration }
end end
} ……
end end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
12. Enumerable#lazy の活用例 11
Enumerable#lazy を上手に活用すると生成、フィルタを順に
処理するプログラムをメソッドチェーンで簡潔に記述できる。
require 'forwardable' def map &block
@lazy = @lazy.map &block
class FizzBuzz self
extend Forwardable end
def_delegators :@lazy, :each def fizzbuzz
map do |i|
def self.each &blk (i % 15 == 0) ? "FizzBuzz" : i
fb = self.new end
fb.fizzbuzz.fizz.buzz. end
take(30).each(&blk) def buzz
end map do |i|
(i % 5 == 0) ? "Buzz" : i
def initialize end
inf = 1.0/0 end
@lazy = (1..inf).lazy def fizz
end map do |i|
(i % 3 == 0) ? "Fizz" : i
def take n end
@lazy = @lazy.take n end
self end
end FizzBuzz.each do |i|
puts i
end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
13. (おまけ)パイプライン処理 12
Enumerator を活用すると、UNIX のパイプのように処理を書ける
下記の例では本当にパイプ(|)で処理する例
ついカッとなってやった。今は反省している。
ふつうはメソッドチェーンで十分実用的
フィルタ処理をオブジェクトにしたいときはこういう方法もベンリかも。
Enumerator.new の書き方に精通していないと、分かりにくい。。。
Enumerable#map とか使って、書けたら分かりやすいのにな。。。
# Enumerable#lazy は無関係だけど。。。
class Pipeline <Enumerator
attr_accessor :source def main
def |(other) fb = FizzBuzz.new(15, "FizzBuzz")
other.source = self buzz = FizzBuzz.new( 5, "Buzz")
other fizz = FizzBuzz.new( 3, "Fizz")
end
end inf = 1.0/0
sink = Pipeline.new(1..inf)
class FizzBuzz <Pipeline pipe = sink | fb | buzz | fizz
def initialize n, subst pipe.take(30).each{|i| puts i}
super() do |y|
@source.each do |i| end
y << (i % n == 0) ? subst : i
end if $0 == __FILE__
end main
end end
end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
14. (おまけ) もっと lazy に 13
クロージャを活用すれば、もっと評価タイミングを遅延できる
値ではなく、クロージャを要素とし、必要なときクロージャを評価する
クロージャなら、memoize も簡単に実装可能
同じ計算を2回以上しないようにできる。
# 下記の例も Enumerable#lazy と関係ない。。。
# memoize しない場合。同じ計算を何度もする。 # memoize版。同じ計算は1度だけ。圧倒的に速い。
# すごい遅い。 def add a, b
memo = nil
def add a, b lambda do
lambda do return memo if memo
return a.() + b.() memo = a.() + b.()
end return memo
end end
end
fib = Enumerator.new do |yielder|
a = b = lambda{ 1 } fib = Enumerator.new do |yielder|
loop do a = b = lambda{ 1 }
yielder << a loop do
a, b = b, add(a, b) yielder << a
end a, b = b, add(a, b)
end end
end
fib.take(36).each do |i|
puts i.() fib.take(36).each do |i|
end puts i.()
end
2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」