トップ «前の日記(2006年08月04日) 最新 次の日記(2006年08月08日)» 編集

ohai日誌

2003|12|
2004|01|02|03|04|05|06|07|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|06|08|10|11|
2008|01|02|03|04|05|07|09|
2009|01|02|
2013|06|12|
2014|01|02|03|04|06|09|10|12|
2015|04|
2016|09|
2018|02|

2006年08月06日

_ Module#define_method

Rubyでは通常メソッドは def を使って定義します。しかしそれ以外にもメソッドを クラスに追加する方法があります。それがdefine_methodです。 このメソッドは与えられたブロック(やMethod,Proc,UnboundMethodのインスタンス) をメソッドとして登録します。例えば以下のようにします。

define_method(:foo){|x,y| 2*x*y}

すると

foo(2,6) # => 24

というように使えます。

このメソッドを使う場合には、スコープに注意すべきです。

  • ブロックの中では、基本的に定義したクラスのインスタンスのコンテキストになる。
    • レシーバを省略したメソッド呼びだし
    • インスタンス変数
    • self
    • etc
  • ただし、ブロックの中から外のローカル変数が見える。

というようになっています。逆にこれがこのメソッドの利点でもあります。 define_methodを使うことで、以下のようなことができます。

  • メソッド名を動的に決めることができる
  • 外側のローカル変数を読み書きできる

1番目はevalを使うことで同じことができますが、2番目はdefine_methodで なければできません。

まずは、attr_accessorのようなものを作ってみましょう。 デフォルト値をあらかじめ指定できるようにしてみましょう。

class Module
  def attr_accsssor_default(name, val)
    iv_name = "@#{name}"
    attr_writer(name)
    define_method(name) do
      if instance_variables.include?(iv_name)
        instance_variable_get(iv_name)
      else
        val
      end
    end
  end
end

class A
  attr_accsssor_default :foo, "foo"
end

a = A.new
p a.instance_variables # => []
p a.foo # => "foo"
p a.instance_variables # => []
a.foo = 3 
p a.instance_variables # => ["@foo"]
p a.foo # => 3

ここでローカル変数(引数)valを読んでいます。instance_variables, instance_variable_get/setは名前そのままの機能です。

次はこれの変形版、インスタンス変数を使わないようにします。

class Module
  def attr_accsssor_default_(name, val)
    define_method("#{name}=") do |new_val|
      val = new_val
    end
    define_method(name) do
      val
    end
  end
end

class A
  attr_accsssor_default_ :bar, Math::E
end

a = A.new

p a.bar # => 2.71828182845905
a.bar = Math::PI 
p a.bar # => 3.14159265358979

ここではvalを読み書き両方しています。

__send__と組合せることでForwardableもどきを作ります。

module Forwardable_
  def def_delegator(accessor, *methods)
    methods.each do |m|
      define_method(m) do |*args|
        instance_variable_get(accessor).__send__(m, *args)
      end
    end
  end
end

class B
  extend Forwardable_
  def_delegator :@baz, :foo, :bar

  def initialize(baz)
    @baz = baz
  end
end

オブジェクトの特異クラスに対するdefine_method、define_singleton_methodを作ります。

class Object
  def define_singleton_method(name, &block)
    (class << self
       self
     end).module_eval{ define_method(name, &block) }
  end
end

特異クラス構文の中でselfが特異クラスオブジェクトになることを利用します。 また、define_methodはprivate methodなのでmodule_evalでそのあたりをを ごまかします。 このようにブロックをたらいまわしにすることでクラスに直接触れずに メソッドを定義することもできます。あまり濫用するとプログラムがわかり にくくなりそうなテクニックですね。

次回はまあ情報取得の類でもしましょう。

_ プログラミング言語について

プログラミング言語について議論するとき○○ができる/できないと いう議論はそれほど意味がありませんよね。簡単にできるか/否か、 わかりやすく書けるか/書けないかが問題になるべきですよね。


トップ «前の日記(2006年08月04日) 最新 次の日記(2006年08月08日)» 編集