• Like
Enumerable lazy について
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

Enumerable lazy について

  • 1,136 views
Published

 

Published in Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,136
On SlideShare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
4
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Enumerable#lazyについて 2012/11/10 cuzic
  • 2. Ruby 2.0.0 について 1 Ruby の生誕20周年にリリース予定 2013年2月24日 公式リリース予定 新機能 実はそれほど多くない 100%互換を目指している 大きいのは、パラメータ引数 と Module#prepend くらい? Enumerable#lazy Ruby 2.0.0 の新機能の1つ 遅延評価っぽい配列演算を扱うことができるライブラリ Esoteric Lang や Ruby ISO 界隈で有名な @yhara さんの作2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 3. Enumerable#lazy 2 Enumerable#lazy を使うと、"遅延評価" される ふつうにやると、無限ループになっちゃってハマる Enumerable#lazy があれば、必要な分を1個ずつ処理する。 無限個のリストでも問題なく処理できる。■ 1,000 より大きな素数を 10個 出力する ■ 1,000 より大きな素数を 10個 出力する 無限ループに陥る 最初の 10個だけが処理されるrequire prime require primeinfinity = 1.0/0 infinity = 1.0/0(1000..infinity).select do |i| (1000..infinity).lazy.select do |i| i.prime? i.prime?end. # ここで、無限ループに陥る end.take(10). # 最初の10個だけを評価する take(10).each do |i| each do |i| puts i puts iend end 2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 4. Enumerable#lazy のベンリなところ 3【Enumerable#lazy を使わなければ】 【Enumerable#lazy なら】 (1) map などの処理ごとに配列を生成する (1) map などの処理ごとに、 ・大量の配列生成をともなうので、 Enumerator::Lazy オブジェクトを生成 メモリ効率が悪い ・すべての要素を配列にしたりしない (2) 最終的に使われなくても、生成する ・大量のメモリを消費したりしない ・もったいない (2) 次に使う分だけ生成する ・下の例だと、10個目より後は不要 ・エコ (3) 無限ループに陥る ・下の例だと、10個目より後は何もしない ・無限個の要素を処理しようとするので (3) 無限を無限のまま扱える 無限ループになっちゃう。 ・宇宙ヤバい■ 1,000 より大きな素数を 10個 ■ ".rb" ファイルを 10個 列挙require prime require findinfinity = 1.0/0 Find.find("/").lazy.select do |f|(1000..infinity).lazy.select do |i| File.file?(f) && f.end_with?(".rb") i.prime? end.take(10).each do |f|end.take(10). # 最初の10個だけを評価する puts f each do |i| end puts iend 2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 5. Enumeratable#lazy の内部動作(1) 4 Enumerable#lazy は Enumerator::Lazy オブジェクトを返す Enumerator::Lazy は Enumerator のサブクラス Enumerator : 外部イテレータを実現するためのクラス 内部では Fiber を使って実装されている Enumerator::Lazy は、Enumerable モジュールのメソッドを再定義 map 、select、take などのメソッドの返り値は、Enumerator::Lazy オブジェクト ※ Enumerable モジュールで増えるメソッドは Enumerable#lazy だけ。 ここで、Enumerator::Lazy オブジェクトを生成。infinity = 1.0/0(1000..infinity).lazy. 1個ずつ処理する。 select do |i| i.prime? select や take などの Enumerable モジュールのメソッドend. は、Enumerator::Lazy クラスでは take(10).each do |i| puts i 1個ずつ処理する Enumerator::Lazy オブジェクトend を返すように再定義されている。2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 6. Enumerable#lazy の内部動作(2) 5 Enumerable#lazy は最初の1個だけがんばって処理。残りは後回し。 2個目以降については、後工程で必要になって初めて、前工程の処理を行う。 inf = 1.0/0 (1000..inf).lazy.select do |i|【lazy Fiber】 (1000..inf).lazy i.prime?値を順に yield する Enumerable::Lazy オブジェクトを生成 end.take(10). # 最初の10個だけ評価次の値を yield して、 lazy Fiber は中断する。 each do |i| puts i 【select Fiber】 select {|i| i.prime?} end 素数である場合だけ yield する E::Lazy オブジェクト 素数となる値が来るまで、親 Fiber に値を要求する。 素数があれば、その値を yield して select Fiber は中断 【take Fiber】 take(10) 最初の 10個であれば yield する E::Lazy オブジェクト 親 Fiber から得られた値を 10個目までは 子Fiber に そのまま yield し、take Fiber は処理を中断する。 【each Fiber】 each {|i| puts i } 親Fiber から受け取った値をそのまま 表示する。 ループごとに親 Fiber に次の値を要求する。 2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 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 endend end# callcc だから遅い # Fiber を使っているから速いg.each do |byte| enum.each do |byte| puts byte puts byteend end2012/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 endend def fib.eachfib.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 endend endfib.each do |i| fibonacci do |i| break if i > 1000 break if i > 1000 puts i 単純なイテレーションend puts i ならいいけど・・・。 endfib.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 について」
  • 10. (おまけ) Enumerator の利用例データ構造に対する巡回方法を Enumerator で抽象化可能 1 2 3 1 4 7マトリックスに対して、行 => 列 の順で列挙するか 4 5 6 2 5 8列 => 行 の順で列挙するか 7 8 9 3 6 9 / ① /bin /usr /etc /home ② ③ ④ ⑤ 木構造に対して、 /usr/local ⑥ /usr/lib ⑦ /home/cuzic ⑧ 幅優先探索を行うか 深さ優先探索を行うか / ① /bin /usr /etc /home ② ③ ⑥ ⑦ /usr/local /usr/lib /home/cuzic ④ ⑤ ⑧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 << valend 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 &blockclass 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 end2012/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") endend 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 endend2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 14. (おまけ) もっと lazy に 13 クロージャを活用すれば、もっと評価タイミングを遅延できる 値ではなく、クロージャを要素とし、必要なときクロージャを評価する クロージャなら、memoize も簡単に実装可能 同じ計算を2回以上しないようにできる。 # 下記の例も Enumerable#lazy と関係ない。。。# memoize しない場合。同じ計算を何度もする。 # memoize版。同じ計算は1度だけ。圧倒的に速い。# すごい遅い。 def add a, b memo = nildef add a, b lambda do lambda do return memo if memo return a.() + b.() memo = a.() + b.() end return memoend end endfib = 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 endfib.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 について」
  • 15. まとめ 14 Enumerable#lazy @yhara さんの作品 無限を無限のまま扱える 次の1個だけ処理して、残りは後回し 全部一気に処理するより、必要なメモリ量が少なくて済む Enumerator Enumerator::Lazy の内部実装で大活躍 Enumerator は Fiber より分かりやすく、実用的。 Fiber でやりたいことの大部分は Enumerator で十分可能 発展的な使い方 メソッドチェーンでパイプライン処理とか クロージャを使ってより遅延評価させたり。2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 16. 15ご清聴ありがとう ございました