Enumerable#lazyについて

      2012/11/10
        cuzic
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 について」
Enumerable#lazy                                      2
  Enumerable#lazy を使うと、"遅延評価" される
  ふつうにやると、無限ループになっちゃってハマる
  Enumerable#lazy があれば、必要な分を1個ずつ処理する。
      無限個のリストでも問題なく処理できる。


■ 1,000 より大きな素数を 10個 出力する                 ■ 1,000 より大きな素数を 10個 出力する
  無限ループに陥る                                  最初の 10個だけが処理される
require 'prime'                           require 'prime'

infinity = 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 i
end                                       end




  2012/11/10 Scala/Clojure/Ruby勉強会   「Enumerable#lazy について」
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 'find'

infinity = 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 i
end

  2012/11/10 Scala/Clojure/Ruby勉強会    「Enumerable#lazy について」
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 について」
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 について」
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 について」
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 について」
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 について」
(おまけ) 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 について」
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 について」
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 について」
(おまけ)パイプライン処理                                               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 について」
(おまけ) もっと 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 について」
まとめ                      14
 Enumerable#lazy
     @yhara さんの作品
     無限を無限のまま扱える
     次の1個だけ処理して、残りは後回し
         全部一気に処理するより、必要なメモリ量が少なくて済む
 Enumerator
     Enumerator::Lazy の内部実装で大活躍
     Enumerator は Fiber より分かりやすく、実用的。
         Fiber でやりたいことの大部分は Enumerator で十分可能
 発展的な使い方
   メソッドチェーンでパイプライン処理とか
   クロージャを使ってより遅延評価させたり。




2012/11/10 Scala/Clojure/Ruby勉強会   「Enumerable#lazy について」
15




ご清聴ありがとう
 ございました

Enumerable lazy について

  • 1.
  • 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 'prime' infinity = 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 i end 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 'find' infinity = 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 i end 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 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 について」
  • 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 << 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 について」
  • 15.
    まとめ 14 Enumerable#lazy @yhara さんの作品 無限を無限のまま扱える 次の1個だけ処理して、残りは後回し 全部一気に処理するより、必要なメモリ量が少なくて済む Enumerator Enumerator::Lazy の内部実装で大活躍 Enumerator は Fiber より分かりやすく、実用的。 Fiber でやりたいことの大部分は Enumerator で十分可能 発展的な使い方 メソッドチェーンでパイプライン処理とか クロージャを使ってより遅延評価させたり。 2012/11/10 Scala/Clojure/Ruby勉強会 「Enumerable#lazy について」
  • 16.