今回はメタプログラミングを読んでいて、試しに自分のローカルで動かしてみたので、それをメモしていこうと思います。
includeとprepend
module M1
def my_method
'M1#my_method()'
end
end
module M2
def my_method
'M2#my_method()'
end
end
class C
prepend M1
include M1 # 無視される
include M2
def my_method
'C#my_method'
end
end
class D < C; end
p '-------------------'
p D.ancestors => [D, M1, C, M2, Object, Kernel, BasicObject]
p C.ancestors => [M1, C, M2, Object, Kernel, BasicObject]
p C.new.my_method => "M1#my_method()"
モジュールM1とM2をそれぞれprepend、includeしたクラスCのインスタンスからmy_methodを実行するとprependしたモジュールM1のmy_methodが呼ばれた。継承チェーンの中でprependされたメソッドはCクラスのメソッドより下にくるため、([M1, C, M2, Object, Kernel, BasicObject]
) Cクラスのメソッドより先に見つかるため、M1のメソッドが呼ばれたのだ。
当然prepend M1をコメントアウトすると、Cクラスのmy_methodが呼ばれる。
# 略
class C
prepend M1
include M1 # 無視される
include M2
end
# 略
p C.new.my_method => "C#my_method()"
また、同じモジュールを2回 prependまたは includeしても2回目の記述は無視される。
refine
Refinementはオープンクラスの問題点であったグローバルに適用されて、気づかぬうちにメソッドを上書きしてしまうなどの問題が発生しない。
module StringExtensions
refine String do
def reverse
"esrever"
end
end
end
module StringStuff
p 'my_string'.reverse
using StringExtensions
p 'my_string'.reverse
end
=> "gnirts_ym"
=> "esrever"
Refinementsが有効になるのはrefineブロックとusingを呼び出したところからモジュールの終わりまでである。トップレベルでusingがいる場合はファイルの終わりまで有効である。
Refinementsが有効になっている限定されたスコープの中では、オープンクラスと同じでメソッドの再定義や追加ができる。
オーバーライドしているモジュール内ではrefineは無効である。
# 先程の続き
mudule StringStuff
p 'my_string'.reverse
end
=> "gnirts_ym"
また、少々謎ではあるが、
class MyClass
def my_method
p 'original my_method'
end
def another_method
my_method
end
end
module MyClassRefinements
refine MyClass do
def my_method
p 'refined my_method'
end
end
end
using MyClassRefinements
MyClass.new.my_method => "refined my_method"
MyClass.new.another_method => "original my_method"
using MyClassRefinementsの後にanother_methodを呼び出しているのに結果はoriginal my_methodが呼ばれている。個々の部分はメタプログラミングの説明を呼んでもよく分からなかった。
さいごに
Rubyというかプログラミングっておもしろい。コツコツやっていきます。