Rubyメタプログラミング

TL;DR

  • classはClassクラスのオブジェクト、moduleはModuleクラスのオブジェクト、である
  • 特異クラスはclass << objectで、あるobjectに特異な(特別な)メソッドをまとめて定義する
  • 特異メソッドdef object.methodで、あるobjectに特異な(特別な)メソッドを定義する
  • 他言語でクラスメソッドと呼ばれるものは、rubyではclassオブジェクトの特異メソッドと言える
  • class << self ... endでは、self.を付けずにclassオブジェクトの特異メソッド(クラスメソッド)を宣言する
  • instance_evalの引数ブロックは、レシーバの特異クラスのコンテキストで実行されるので、アクセサ(getter)の定義されていないインスタンス変数にもアクセスできるし、新たなインスタンス変数を与えることもできる
  • include moduleはミックスインであり、C++の多重継承やJavaのインターフェースが無いRubyにおいて、共通化されたメソッドをオブジェクトに結びつける手段となる
  • module_functionはメソッドのprivate指定、かつ、moduleオブジェクトにおける特異メソッド
  • オブジェクトa1のメソッドm14の呼び出しにおいて、instance_evalで新たに与えたインスタンス変数@jへアクセスできている点から、オープンクラスのモンキーパッチでclass Aに与えたメソッドm14が、a1の特異クラスのコンテキストで実行されていることが分かる
  • class Aのmethod_missingでdefine_methodしたインスタンスメソッドm16を、AのサブクラスA_のインスタンスa3から呼び出すことができる
  • sendは可視性を問わずアクセスできるので、private指定したメソッドm18を呼び出せる

プログラム

$ cat meta.rb 
puts "== Self check outside of class =="
p self
p self.class

module M
    puts "== Self check inside of module =="
    p self
    p self.class
    def m10;p __method__ end
    def m11;p __method__ end
    def m12;p __method__ end
    module_function :m11,:m12
end

class A
    puts "== Self check inside of class =="
    p self
    p self.class
    include M
    def initialize(i)
        puts "== Self check inside of method =="
        p self
        p self.class
        @i=i
    end
    def m1;p __method__ end
    def self.m2;p __method__ end
    class << self
        def m3;p __method__ end
    end
    def m12; puts __method__.to_s+"!!" end
end

def A.m4;p __method__ end

a1=A.new(1)
a2=A.new(2)

A_=Class.new(A){|c|
    def m15; p __method__ end
}
a3=A_.new(3)

puts "== class check =="
p A.class
p M.class
p a1.class
p a2.class
p a3.class
puts ""

def a1.m5;p __method__ end
puts "== class A methods list == "
p A.methods.grep(/^m[0-9]/)
puts "== class A instance methods list == "
p A.instance_methods.grep(/^m[0-9]/)
puts "== module M methods list == "
p M.methods.grep(/^m[0-9]/)
puts "== module M instance methods list == "
p M.instance_methods.grep(/^m[0-9]/)
puts "== object a1 methods list == "
p a1.methods.grep(/^m[0-9]/)
puts "== object a2 methods list == "
p a2.methods.grep(/^m[0-9]/)
puts ""

puts "== object a1 instance_eval == "
a1.instance_eval{p@i}
a1.instance_eval{@j=@i*10}
a1.instance_eval{p@j}
puts "== object a2 instance_eval == "
a2.instance_eval{p@i}
a2.instance_eval{@i=@i*20}
a2.instance_eval{p@i}
a2.instance_eval{p@j}
puts ""

puts "== Call instance_eval for m6 and m7 =="
instance_eval File.read('meta.txt')
puts ""

puts "== Call define_method for m8 =="
class<<A;define_method(:m8){p __method__}end
puts ""

puts "== Call class_eval and define_method for m9 =="
pc=Proc.new{p __method__}
A.class_eval{define_method(:m9,pc)}
puts ""

puts "== Open class, monkey patch for m13 and m14 =="
class A
    def m13; p @i end
    def m14; p @j end
end
puts ""

puts "== Define method_missing for class A and its instance =="
class A
    def self.method_missing(m,*a)
        puts __method__.to_s+"(#{m})"
        define_method(m){p __method__}
    end
    def method_missing(m,*a)
        puts __method__.to_s+"<#{m}>"
    end
end
puts ""

module M_
    def m17;p __method__ end
    def m18;p __method__ end
    private :m18
end

puts "== Extend M_ with a3 =="
a3.extend(M_)
puts ""


r=(0..18)
puts "== class A methods respond_to? check =="
r.each{|n|print "m#{n} "; if A.respond_to?"m#{n}".to_sym; eval "A.m#{n}"; else puts "-" end}
puts "== module M methods respond_to? check =="
r.each{|n|print "m#{n} "; if M.respond_to?"m#{n}".to_sym; eval "M.m#{n}"; else puts "-" end}
puts "== object a1 methods respond_to? check =="
r.each{|n|print "m#{n} "; if a1.respond_to?"m#{n}".to_sym; eval "a1.m#{n}"; else puts "-" end}
puts "== object a2 methods respond_to? check =="
r.each{|n|print "m#{n} "; if a2.respond_to?"m#{n}".to_sym; eval "a2.m#{n}"; else puts "-" end}
puts "== class A_ methods respond_to? check =="
r.each{|n|print "m#{n} "; if A_.respond_to?"m#{n}".to_sym; eval "A_.m#{n}"; else puts "-" end}
puts "== module M_ methods respond_to? check =="
r.each{|n|print "m#{n} "; if M_.respond_to?"m#{n}".to_sym; eval "M_.m#{n}"; else puts "-" end}
puts "== object a3 methods respond_to? check =="
r.each{|n|print "m#{n} "; if a3.respond_to?"m#{n}".to_sym; eval "a3.m#{n}"; else puts "-" end}
puts ""

puts "== Call m16 from a1, A, a2 and a3 =="
a1.m16
A.m16
a2.m16
a3.m16
puts ""

puts "== Send m17, private method m18 and call m18 from a3 =="
a3.send("m17")
a3.send("m18")
a3.m18
puts ""

DSL

$ cat meta.txt 
def A.m6;p __method__ end
def a2.m7;p __method__ end

実行結果

$ ruby meta.rb
== Self check outside of class ==
main
Object
== Self check inside of module ==
M
Module
== Self check inside of class ==
A
Class
== Self check inside of method ==
#<A:0x007f86009fb1a8>
A
== Self check inside of method ==
#<A:0x007f86009fb018>
A
== Self check inside of method ==
#<A_:0x007f86009fade8>
A_
== class check ==
Class
Module
A
A
A_

== class A methods list == 
[:m2, :m3, :m4]
== class A instance methods list == 
[:m1, :m12, :m10]
== module M methods list == 
[:m11, :m12]
== module M instance methods list == 
[:m10]
== object a1 methods list == 
[:m5, :m1, :m12, :m10]
== object a2 methods list == 
[:m1, :m12, :m10]

== object a1 instance_eval == 
1
10
== object a2 instance_eval == 
2
40
nil

== Call instance_eval for m6 and m7 ==

== Call define_method for m8 ==

== Call class_eval and define_method for m9 ==

== Open class, monkey patch for m13 and m14 ==

== Define method_missing for class A and its instance ==

== Extend M_ with a3 ==

== class A methods respond_to? check ==
m0 -
m1 -
m2 :m2
m3 :m3
m4 :m4
m5 -
m6 :m6
m7 -
m8 :m8
m9 -
m10 -
m11 -
m12 -
m13 -
m14 -
m15 -
m16 -
m17 -
m18 -
== module M methods respond_to? check ==
m0 -
m1 -
m2 -
m3 -
m4 -
m5 -
m6 -
m7 -
m8 -
m9 -
m10 -
m11 :m11
m12 :m12
m13 -
m14 -
m15 -
m16 -
m17 -
m18 -
== object a1 methods respond_to? check ==
m0 -
m1 :m1
m2 -
m3 -
m4 -
m5 :m5
m6 -
m7 -
m8 -
m9 :m9
m10 :m10
m11 -
m12 m12!!
m13 1
m14 10
m15 -
m16 -
m17 -
m18 -
== object a2 methods respond_to? check ==
m0 -
m1 :m1
m2 -
m3 -
m4 -
m5 -
m6 -
m7 :m7
m8 -
m9 :m9
m10 :m10
m11 -
m12 m12!!
m13 40
m14 nil
m15 -
m16 -
m17 -
m18 -
== class A_ methods respond_to? check ==
m0 -
m1 -
m2 :m2
m3 :m3
m4 :m4
m5 -
m6 :m6
m7 -
m8 :m8
m9 -
m10 -
m11 -
m12 -
m13 -
m14 -
m15 -
m16 -
m17 -
m18 -
== module M_ methods respond_to? check ==
m0 -
m1 -
m2 -
m3 -
m4 -
m5 -
m6 -
m7 -
m8 -
m9 -
m10 -
m11 -
m12 -
m13 -
m14 -
m15 -
m16 -
m17 -
m18 -
== object a3 methods respond_to? check ==
m0 -
m1 :m1
m2 -
m3 -
m4 -
m5 -
m6 -
m7 -
m8 -
m9 :m9
m10 :m10
m11 -
m12 m12!!
m13 3
m14 nil
m15 :m15
m16 -
m17 :m17
m18 -

== Call m16 from a1, A, a2 and a3 ==
method_missing<m16>
method_missing(m16)
:m16
:m16

== Send m17, private method m18 and call m18 from a3 ==
:m17
:m18
method_missing<m18>