メタプログラミング Ruby 勉強メモ
Ruby を勉強し直そうと思い進められたのがこれ。
メタプログラミングRuby
Paolo Perrotta
固定リンク: http://www.amazon.co.jp/dp/4048687158
ググると Ruby 2.0 に対応した英書が出ているようですが、
とりあえず1.9に対応している和書でお勉強。
オブジェクトモデル
オープンクラス
既存のクラスに新たに独自のメソッドを追加すること
例えば整数を2倍したかったら普通はこうかく。
def to_twice(num)
num * 2
end
puts to_twice(2) # => 4
それをオープンクラスを利用して書き換えると
class Fixnum
def to_twice
self * 2
end
end
puts 2.to_twice # => 4
こうなる。
標準の Fixnum クラスに独自の自身を2倍するメソッドを追加できた。
すでにクラスに同名のメソッドがあると上書きしてしまうので注意
(これをモンキーパッチというらしい)
クラス
クラスもオブジェクトらしい。
どういうこと?
puts "hello".class # => String
puts String.class # => Class
これは String クラスが Class クラスのオブジェクトということを示している。
メソッドの呼び出し
Ruby ではメソッドの呼び出しを以下の順で行う
- メソッドを探す これをメソッド探索という
- メソッドを実行する。これには self というものが必要
例えば
class Hoge
def say
puts "hogehoge"
end
end
class Hige < Hoge
end
hi = Hige.new
hi.say #=> "hogehoge"
Hoge クラスがもつ say メソッド
↑
hi オブジェクト → Hige クラス
みたいな感じで呼ばれる。
これを「右へ一歩、それから上へ」ルールと呼ぶ。
そしてこのクラスのつながりを継承チェーンと呼ぶ
じゃこの場合はどうなるか?
module A
def say
puts "A"
end
end
module B
def say
puts "B"
end
end
class C
include A
include B
end
class D < C
end
d = D.new
d.say # ここで出力されるのは A ? それとも B ?
こたえば B。
なぜかというと、module をインクルードすると継承チェーンでクラスの真上にインクルードした module がつながるから。
図にすると
A モジュール
↑
B モジュール
↑
C クラス
↑
D クラス
という順になる。最初はCクラスの真上にAモジュールがつながり、
つぎにBをインクルードしたのでCクラスの真上にBモジュールがつながるため。
メソッド探索は下から行うので最初に見つかる say メソッドは B モジュールのメソッドになる。
メソッド
Ruby は動的にメソッドを定義したり呼び出したりできる。
send
その前に send について少しだけ。
メソッドの呼び出し方は2つある。
class Hoge
def say (name)
puts "Hello, #{name}"
end
end
h = Hoge.new
h.say("issei")
h.send(:say, "issei")
見ての通り、オブジェクト.send(<呼びたいメソッド>, <引数>)でメソッドが呼べちゃう。
何が起きるかって言うと、呼びたいメソッドを変数にすることができちゃう。
これを動的ディスパッチと呼ぶ
define_method
実行的にメソッドを定義することもできる。
class Hoge
define_method :say do |name|
puts "Hello, #{name}"
end
end
h = Hoge.new
h.send(:say, "issei") # => "Hello, issei"
send と define_method を使うと面白いことができるようになる。
動的メソッド呼び出しの例
書中では Test::Unit の例が出されていた。
method_names = public_instance_method(true)
tests = method_names.delete_if {|method_name| method_name != /^test./
これでtestと先頭についてメソッドを収集して send で実行しているよう。
動的メソッド定義の例
たとえばこんなクソコードを考えてみる。
プリンタっぽいものを想定した KusoPrinter クラス。
class KusoPrinter
def initialize(paper_size)
@paper_size = paper_size
end
def print_A4
puts @paper_size.get_info_A4
end
def print_B4
puts @paper_size.get_info_B4
end
end
class PaperSize
def get_info_A4
return "A4 desu"
end
def get_info_B4
return "B4 desu"
end
end
ps = PaperSize.new
prt = KusoPrinter.new(ps)
prt.print_A4 #=> "A4 desu"
ほとんど同じメソッドがいっぱい並んでる。
似た部分を抽出してメソッドを作る。
class KusoPrinter
def initialize(paper_size)
@paper_size = paper_size
end
def print_A4
self.print_ps("A4")
end
def print_B4
self.print_ps("B4")
end
def print_ps(paper_size)
puts @paper_size.send("get_info_#{paper_size}")
end
end
class PaperSize
def get_info_A4
return "A4 desu"
end
def get_info_B4
return "B4 desu"
end
end
ps = PaperSize.new
prt = KusoPrinter.new(ps)
prt.print_A4 #=> "A4 desu"
このままだと用紙サイズを増やす必要がある場合、
そのたびにメソッドを追加しなければならない
これを send と define_method を使うと
class KusoPrinter
def initialize(paper_size)
@paper_size = paper_size
end
def self.print_ps(paper_size)
define_method "print_#{paper_size}" do
puts @paper_size.send("get_info_#{paper_size}")
end
end
print_ps("A4")
print_ps("B4")
end
class PaperSize
def get_info_A4
return "A4 desu"
end
def get_info_B4
return "B4 desu"
end
end
ps = PaperSize.new
prt = KusoPrinter.new(ps)
prt.print_A4 #=> "A4 desu"
と重複するコードを削除することができた。
さらにPaperSizeクラスでのget_info_xxxx メソッドが増えても、
KusoPrinter クラスを書き換えないで済むようにできる。
class KusoPrinter
def initialize(paper_size)
@paper_size = paper_size
@paper_size.methods.grep(/^get_info_(.*)$/) {
KusoPrinter.print_ps($1)
}
end
def self.print_ps(paper_size)
define_method "print_#{paper_size}" do
puts @paper_size.send("get_info_#{paper_size}")
end
end
end
class PaperSize
def get_info_A4
return "A4 desu"
end
def get_info_B4
return "B4 desu"
end
def get_info_B3
return "B3 desu"
end
end
ps = PaperSize.new
prt = KusoPrinter.new(ps)
prt.print_A4 #=> "A4 desu"
prt.print_B3 #=> "B3 desu"
PaperSize がもつメソッド名からKusoPrinter のメソッドを動的に生成している!
すごい!
手軽な図を書く方法、ソフトあれば教えてください!
umanodaさん
ソースコードの間違い訂正ありがとうございます!