読者です 読者をやめる 読者になる 読者になる

Rubyについて~その2

こんにちは、早くも次のターンがまわってきました、south37です。
前回の宣言通り、rubyにおけるメタプログラミグについて書きたいと思います。
ちなみに、今日書くような内容は「メタプログラミングRuby」という本にそっくり書かれているので、興味を持った方は購入してみるのもいいと思います。
これまで読んできた技術書の中でも飛び抜けて面白かったので、オススメですよ!!

Rubyにおけるメタプログラミング

rubyにおけるメタプログラミングの一番分かり易い例は、「メソッドを作るメソッド」の存在です。例えば、前回紹介したattr_accessorなどは、引数として受け取ったシンボルを元にgetter/setterもメソッドを作ります。こういったattr_accessorのようなメソッドが自分でも簡単に定義出来るのがrubyの魅力でもあります。
例えば、単純なattr_accessorは、次のようなコードで実現出来てしまいます。

class MyClass
  def self.my_attr_accessor(attr_sym)
    define_method attr_sym do
      @attr
    end

    define_method "#{attr_sym}=" do |value|
      @attr = value
    end
  end

  my_attr_accessor :name
end

my_object = MyClass.new
my_object.name = "name"
puts my_object.name
# >> name

順に解説していきたいと思います。
まず、最初にdef self.~と書いているのは、クラスメソッドを定義しています。self.をつけるというのがポイントです。すなわちclass式内部ではselfはそのクラス自身となっている為、self.~と書く事でクラスオブジェクトのインスタンスメソッドであるクラスメソッドを定義出来る訳です。最後にクラス内でmy_attr_accessorが呼び出せているのは、クラスメソッドだからです。
my_attr_accessorの中では、define_methodというメソッドを使っています。define_methodは、引数として受け取った文字列orシンボルをメソッド名として持ち、受け取ったブロックを処理内容として持つようなメソッドを定義するメソッドです。上の例だと、my_attr_accessor :nameというコードが実行される事で、

class MyClass
  def name
      @attr
  end

  def name=(value)
      @attr = value
  end
end

で定義されるのと同じnameとname=メソッドが作られる事が分かります。
ちなみに、ブロックとはdoとendで囲まれたコードで、rubyの中では頻出します。他の言語であまり対応する概念が無い為最初は戸惑いますが、使っているとすぐに慣れます。コードのかたまりを受け渡し出来るのでめちゃくちゃ便利です。do~endだけではなく、{~}みたいに中括弧で囲んだ形でも用いられます。ブロックには受け取る引き数を指定する事も出来て、例えば上の例ではdoの後の|value|というのがブロックの引数です。
このようなメソッドを定義するメソッドが標準で用意されている事から、rubyではメタプログラミングが容易に出来ます。
ちなみに、my_attr_accessorには実は不備があります。インスタンス変数名として@attrというのを使っているため、複数のgetter/setterというのが作れません。本来ならば、@nameというインスタンス変数を用いるべきです。
実は、rubyではこういった問題も簡単に解決出来ます。rubyにはinstance_variable_setinstance_variable_getといったメソッドが用意されており、動的にインスタンス変数を作る事が出来ます。これらを用いて上記のコードを書き直すと、

class MyClass
  def self.my_attr_accessor(attr_sym)
    define_method attr_sym do
      instance_variable_get "@#{attr_sym}"
    end

    define_method "#{attr_sym}=" do |value|
      instance_variable_set "@#{attr_sym}", value
    end
  end

  my_attr_accessor :name
end

といった感じになります。これで、@nameに関してのgetter/setterであるnameやname=メソッドが作られる訳です。

ここまで、define_methodによる動的なメソッド定義について説明してきた訳ですが、rubyにおいてはもう一つ代表的なメタプログラミングの方法があります。それは、method_missingメソッドを利用するというものです。

method_missingを利用したメタプログラミング

method_missingは、定義されていないメソッドを呼び出した時に呼び出されるメソッドです。第一引数として呼び出されたメソッド名のシンボル、第二引数としてそのメソッドの引数を受け取り、処理を書く事が出来ます。例えば、任意に属性を追加出来る様にしたい場合には、

class MyClass
  def method_missing(name, *args)
    if name =~ /=/
      instance_variable_set('@' + name.to_s.sub(/=/, ''), args.first)
    else
      instance_variable_get('@' + name.to_s)
    end
  end
end

my_object = MyClass.new
my_object.name = 'name'
puts my_object.name
# >> name

のようなコードを書けば良いです。
ちなみに、上の例を解説すると、=~というのは文字列又はシンボルが正規表現にマッチするかどうかを確かめるメソッドです。/=/というのが正規表現で、これは文字列中の=にマッチします。ようは、name中に=が含まれているか確かめている訳です。マッチしたら、setterという事なので、my_attr_accessorを定義した時と同様にinstance_variable_setを呼び出します。その際、nameはシンボルである為、to_sメソッドで文字列へと変換し、さらにsubメソッドで=を空文字列へと置換しています。そうして属性名だけを取り出した後、@と連結してインスタンス変数名を生成しています。これで、求める機能は実現出来ました。
ただし、method_missingはこういったただのaccessorの定義の為には普通使いません。メソッド名を受け取って処理を書くという性質上、メソッド名に規則を持たせて、その規則に従うような処理を行わせるのが普通です。例えば、http://mysite/store/indexというパスが欲しい時があるとします。その時、http://mysite/の部分を文字列として持っておいて、毎回文字列を連結してパスを作るのもいいですが、

class MyClass
  def method_missing(name, *args)
    if name =~ /_path/
      'http://mysite/' + name.to_s.gsub(/_path/, '').split('_').join('/')
    end
  end
end

my_object = MyClass.new
my_object.store_index_path                    # => "http://mysite/store/index"
my_object.store_create_path                   # => "http://mysite/store/create"

のように、~_pathという規則性を持たせたメソッドでパスを動的に生成するといった事も出来ます。
上の例ではイマイチ伝えきれてない気もしますが、メソッド名のルールから一意に処理内容が定まるという、普段のコーディングでも意識して行う事が自動で出来てしまう優れた機能だと思います。上記のような「存在しないメソッド呼び出し」によるメソッド実行は、「ゴーストメソッド」とも呼ばれ、メタプログラミングの一つとして親しまれています。

とりあえず、rubyについてはこんなものでしょうか。次からはまた、railsについての話に戻りたいと思います。よろしくお願いします。