せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】【メタプログラミング】遅延評価のためのプロキシオブジェクト、作り方。

 
■topic summary
study about "lazy evaluation" using method_missing and call method.

 

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

勉強中。。


 

遅延評価のためのプロキシオブジェクト

定義時ではなく、呼び出し時に評価するオブジェクトの作成方法。

フラグを用いた方法は以前実装したけど、今回は
・任意のブロックを実行する
・method_missingを利用して任意のメソッド呼び出しに対応する

が、異なる点。
 

module NaiveLazy
  class Promise < BasicObject
    def initialize(&b)
      @computation = b
    end
    def __result__
      if @computation
        @result = @computation.call
        @computation = nil
      end
      @result
    end
    def method_missing(*a, &b)
      __result__.send(*a, &b)
    end
  end
end

class Cell
  def initialize(text)
    @text = text
    @width = NaiveLazy::Promise.new{ calculate_width }
  end
  attr_accessor :text, :width
  def calculate_width
    p "creating----- " + (@text * 10)
  @text * 10
  end
end
cell = Cell.new("h")
p "---- create cell ----"
cell.width        #<= inspectメソッドがmissing
cell.width + "dayo"  #<= +メソッドがmissing

 
■実行結果

"---- create cell ----"
"creating----- hhhhhhhhhh"

 
calculate_widthは、定義時に実行されていないのが分かる。

 

inspectメソッドは独自に定義する

inspectメソッドを独自に定義することで、評価前/後か判断できる。

def inspect
  return "<NaiveLazy::Promise computation=#{@computation.inspect}" if @computation
  return  @result.inspect
end
cell = Cell.new("h")
p cell.width        #<= inspectメソッドが呼び出される
cell.width + "dayo" #<= @resultが生成される
p cell.width        #<= inspectメソッドが呼び出される

 
 
■実行結果

<NaiveLazy::Promise computation=#<Proc:0x26b34e8@C:/Users/_ADMIN/Desktop/test/arrays.rb:26>
"hhhhhhhhhh"

inspect自体では、
newメソッドで渡したブロックの評価が行われないようになった。


 

全文

module NaiveLazy
  class Promise < BasicObject
    def initialize(&b)
      @computation = b
    end
    def __result__
      if @computation
        @result = @computation.call
        @computation = nil
      end
      @result
    end
    def inspect
      return "<NaiveLazy::Promise computation=#{@computation.inspect}" if @computation
      return  @result.inspect
    end
    def method_missing(*a, &b)
      __result__.send(*a, &b)
    end
  end
end

class Cell
  def initialize(text)
    @text = text
    @width = NaiveLazy::Promise.new{ calculate_width }
  end
  attr_accessor :text, :width
  def calculate_width
    @text * 10
  end
end
cell = Cell.new("h")
p cell.width        #<= inspectメソッドが呼び出される
cell.width + "dayo"  #<= +メソッドがmissing
p cell.width        #<= inspectメソッドが呼び出される

 

 

ポイント

BasicObject(白紙のオブジェクト)を使う。

Objectを使うと、煩雑なメソッドを一個一個消していったのは前にやったところ

class TraceObject
  #TraceObjectの既存インスタンスメソッドを消す
  instance_methods.each do |m|
    next if m == :inspect || m == :object_id ||  m == :__id__ || m == :__send__
    undef_method m
  end
・・・

 

ふむー。。