トップ «前の日記(2010-04-01) 最新 次の日記(2010-04-03)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2010-04-02

_ Procを引きずり回す

RjbがimportしたJavaのクラスをRjbはObjectとして持っている。

require 'rjb'
jstring = Rjb::import('java.lang.String')
p jstring # => #<Rjb::Java_lang_String:0x2f0490>

だから、newメソッドは他のクラスと異なりインスタンスメソッドとなっている。

p jstring.method(:new) #=> #<Method: #<Rjb::Java_lang_String:0x2d0334>.new>
p Array.method(:new) #=> #<Method: Class#new>

そのため、newメソッドに介入して作られたJavaオブジェクトのプロクシにメソッドを追加したりできる。

module RjbAddon
  def java_class
    self._classname
  end
end
jstring.instance_eval do
  alias :org_new :new
  def new(*args)
    pxy = org_new(*args)
    pxy.extend RjbAddon
    pxy
  end
end
hello = jstring.new('hello')
p hello.to_string   # => "hello" /* Java method */
p hello.java_class # => "java.lang.String" 

ここで、RjbのJavaクラスのnewメソッドに介在してメソッドを追加する汎用の仕組みを考える。

module RjbAddon
  def java_class
    self._classname
  end
  def self.mixin(jclass, &proc)
    jclass.instance_eval do
      alias :org_new :new
      def new(*args)
        pxy = org_new(*args)
        pxy.extend RjbAddon
        pxy
      end
    end
  end
end
RjbAddon.mixin(jstring)

RjbAddon.mixinに追加のブロックを与えて、そこで定義したメソッドを組み込ませたい。

RjbAddon.mixin(jstring) do  # こう書けるようにしたい。
  def say_hello
    puts 'hello'
  end
end
hello = jstring.new('hello')
hello.say_hello  # => 'hello'

しかし、次のようにmixinメソッドを定義してもうまくいかない。

module RjbAddon
  ...
  def self.mixin(jclass, &proc)
    jclass.instance_eval do
      alias :org_new :new
      def new(*args)
        pxy = org_new(*args)
        pxy.extend RjbAddon
        pxy.instance_eval do
          proc.call  # => tried to create Proc object without a block (ArgumentError)
        end
        pxy
      end
    end
  end
end

instance_evalの中でスコープが変わる(通常のブロックとは違うわけだ)ので、procという仮引数が見えなくなるからだ。

そこで、RjbのJavaオブジェクトのインスタンス変数としてみる。

  def self.mixin(jclass, &proc)
    jclass.instance_eval do
      @user_new = proc
      alias :org_new :new
      def new(*args)
        p @user_new   # => # 見える
        pxy = org_new(*args)
        pxy.extend RjbAddon
        pxy.instance_eval do
          p @user_new # => nil ここのselfはpxyなので見えない
          @user_new.call
        end
        pxy
      end
    end
  end

そこで外ざしできるようにする。

  def user_initialize(proc) # 外ざし用メソッド(ミキシンする)
    instance_eval do
      proc.call
    end  
  end
  def self.mixin(jclass, &proc)
    jclass.instance_eval do
      @user_new = proc
      alias :org_new :new
      def new(*args)
        p @user_new
        pxy = org_new(*args)
        pxy.extend RjbAddon
        pxy.user_initialize @user_new # 外ざし
        pxy
      end
    end
  end

しかし、これもうまくいかない。

p hello.say_hello # => in `method_missing': Fail: unknown method name `say_hello' (RuntimeError)

与えたprocのselfがmainのままだからだ。

しょうがないので、ミキシンメソッド呼び出し時に与えるブロックでselfを取れるようにしてみる。

module RjbAddon
  def java_class
    self._classname
  end
  def user_initialize(proc)
    instance_eval do
      proc.call self   # instance_eval内のself
    end  
  end
  def self.mixin(jclass, &proc)
    jclass.instance_eval do
      @user_new = proc
      alias :org_new :new
      def new(*args)
        pxy = org_new(*args)
        pxy.extend RjbAddon
        pxy.user_initialize @user_new
        pxy
      end
    end
  end
end
RjbAddon.mixin(jstring) do |x|  # xはプロクシ自身
  def x.say_hello               # 自身の特異メソッドなので利用可能
    puts 'hello'
  end
end
...
hello.say_hello  # => hello

しかし、mixinメソッドにブロックを与えて、そこにプロクシのselfが渡されるのは気に食わない。(def x.say_hello と書くところ)

どうすればmixinメソッドに与えるブロックのselfをそのまま作成されたプロクシのselfとできるだろうか?

本日のツッコミ(全10件) [ツッコミを入れる]
_ なかだ (2010-04-03 03:30)

org_new = method(:new)<br>define_method(:new) do |*args|<br> pxy = org_new.call(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval(&proc)<br> pxy<br>end

_ arton (2010-04-03 09:33)

ああ、一番の問題は、instance_evalでブロックを作っていることなのか。&proc として与えれば良いということに気づいてませんでした。<br>define_methodはこの場合あまり使いやすくなかったので、こんな感じ。<br> def self.mixin(jclass, &proc)<br> jclass.instance_eval do<br> @user_new = proc<br> alias :org_new :new<br> def new(*args)<br> pxy = org_new(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval &@user_new<br> pxy<br> end<br> end<br> end<br>でも、define_methodを使うとインスタンス変数が不要になるのは良いかも。<br> def self.mixin(jclass, &proc)<br> define_method(:user_init) do<br> instance_eval(&proc)<br> end<br> jclass.instance_eval do<br> alias :org_new :new<br> def new(*args)<br> pxy = org_new(*args)<br> pxy.extend RjbAddon<br> pxy.user_init<br> pxy<br> end<br> end<br> end

_ arton (2010-04-03 09:38)

あ、最後に書いたやつはだめでした。user_initはあくまでもjclassのインスタンスメソッドにしなければならないから(RjbAddonモジュールのメソッドではない)<br>。

_ なかだ (2010-04-03 10:21)

同じjclassについて二回呼んだ場合<br>* aliasを使うとorig_newが無限再帰になる<br>* インスタンス変数は上書きされる<br>に注意

_ なかだ (2010-04-03 10:27)

orig_newはsuperでいいんじゃないかな

_ なかだ (2010-04-03 10:30)

あ、二回呼んだら前のが無視されちゃうか

_ arton (2010-04-03 12:12)

>orig_newはsuperでいいんじゃないかな<br>Class.newじゃないからだと思うけどsupreに変えたら、unknown method name 'new' (RuntimError)になりました。

_ arton (2010-04-03 12:19)

>* aliasを使うとorig_newが無限再帰になる <br>確かに(試した: `org_new': stack level too deep (SystemStackError)。取り出したら保存が必要ですね。<br> def self.mixin(jclass, &proc)<br> jclass.instance_eval do<br> @user_new = proc<br> alias :org_new :new<br> @org_new ||= method(:org_new)<br> def new(*args)<br> pxy = @org_new.call(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval &@user_new<br> pxy<br> end<br> end<br> end

_ なかだ (2010-04-03 13:50)

Methodで保存するならalias不要

_ arton (2010-04-03 14:00)

そりゃそうか。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|

ジェズイットを見習え