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

wolfmasa's blog

フロンターレとプログラミング関係の話題を、気が向いたときにつぶやくブログです。

メタプログラミングRuby パート1

Rubyを学習する上で、避けて通れないメタプログラミングRubyを読んでいる。

大事なことなので、メモを残しておこうと思う。

ちなみに、読むのは2回目だが、難しい内容が多くて、前回は半分くらいからおいてけぼりをくってしまったので、今回は改めてリベンジを。

第1章:頭文字M

ActivereRecordの例を用いて、メタプログラミングとはという定義を紹介する章。

class Movie < ActiveRecord::Base
end

movie = Movie.create
movie.title = 'タイトル'
movie.title #=> 'タイトル'

のように、定義されていないフィールドを動的に生成する。

第2章:オブジェクトモデル

オープンクラスと、オブジェクトの中身、そしてメソッド探索の方法だ。 この辺まではまだついていける。

と思ったら、オブジェクトのインスタンス変数やメソッドの格納方法を理解していないことに気づく。1回読んだくらいではなかなか頭に入らない。。。

考えれば当たり前ではあるが、インスタンス変数はオブジェクトに、メソッドはそのクラスに格納されている。

オブジェクトはクラスのインスタンスであるが、クラスはClassクラスのインスタンスである。つまり、クラス変数は、Classクラスのインスタンスという観点で考えれば、そのインスタンス変数に過ぎないということである。

また、ClassクラスはModuleクラスを継承しており、各クラスはObjectクラスを継承しているという関係を持っている。継承するということは、継承元のメソッドが使えるということだ。これは、継承元の、さらに継承元のメソッドも使えるということになる。

こう考えていくと、継承とインスタンス化の区別がややこしく思えてくる。

継承とは、メソッドが存在しない場合に探索をたどっていくパスである、これはわかりやすい。継承は続くよ、どこまでも。

インスタンス化とは、クラス変数やクラス(共通の)メソッドを格納している先だとも言えるし、複数のインスタンスをグルーピングした取りまとめ先がクラスとも言える。

この考え方を元にして、メソッドの探索が行われる。

メソッドの探索は、 ・オブジェクト自身が持つかどうか? ・オブジェクトのクラスが持つか? ・オブジェクトのクラスの継承元のクラスが持つか? ・オブジェクトのクラスの継承元のクラスの継承元のクラスが持つか? ・・・ とたどっていく。あら簡単。

だいたいこれで2章は終わりだが、途中でさらりとネームスペースのことが書かれている。

ライブラリを作ったり、ソースを読む上では、個人的にはこれが結構重要だと思う。

module MyNameSpace
  class MyClass
  end
end

としておいて、MyNameSpace::MyClassでアクセスするのは割とよく使う。

第3章:メソッド

この辺から徐々に難解な話になってくるが、まだまだ私でも理解可能な範囲。

そもそも動的にメソッドを呼ぶだけであれば、obj.send(:method_name)を用いればよい。

sendメソッドは、かなりアルティメットウェポン的な感じで、privateだろうとなんだろうと、呼び出せてしまう。カプセル化もクソもあったものではない。(いやいい意味です、たぶん)

似たようなメソッドを用意する必要がある場合に、メソッドを動的に用意することでコードの重複を避けようという話。コピペの嵐になるよりは、スマートで、メンテナンスしやすくて、なによりもコードがかっこいい。

ここでは、メソッドを動的に定義する方法を2種類紹介している。

1.define_methodを利用する

あらかじめメソッド名がわかっていれば、文字列として保持しておき、define_methodで同じようなメソッドを動的に生成することが可能だ。

class MyClass
  define_method :method_name do |arg|
    # 2乗する関数
    arg*arg
  end
end

まぁ、スマートかどうかは微妙だが(よほど規則的で大量のメソッドがあれば別だけど)、define_methodの応用はもっと高度なところにあると思う。

実際にやっているかはわからないけど、たとえばDBなどの外部要因に応じてメソッドを用意しておくこともできるし、微妙に変えたメソッド、たとえばto_XXXのようなサポートメソッドなども簡単に作成できる。

2.method_missingを利用する

継承をたどっていき、メソッドがない場合には、対象オブジェクトのmethod_missingを呼び出すため、あらかじめmethod_missingを定義しておき、その中で引数に応じた処理を行う方法。

method_missingはtypoした時などに、発生しないとも限らないので、他のコードへの影響という意味では、ちょっと危険なにおいがする。個人的には。