メタプログラミングとは何か
メタプログラミングという本をご存知だろうか。

- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2010/08/28
- メディア: 大型本
- 購入: 18人 クリック: 533回
- この商品を含むブログ (127件) を見る
Rubyを勉強し始めたときに、色んな人に薦められて読んだ本。これを読むと、rubyの全体観を把握して、特徴をつかむことができます。 今回は、rubyの認定試験などを受けるにあたって、復習として精読して見ようと思います。 章ごとにまとめる予定です。今回はスタートの第1章。
コードを記述するコードを記述すること
例1) ActiveRecord::Base
class Movie < ActiveRecord::Base
といった形でActiveRecordクラスを承継することで、
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})"
なんてSQL文を書く必要もなく、
movie = Movie.create movie.title ="博士の異常な愛情" movie.save
といったメソッドが使用可能になる。 ActiveRecordは、オブジェクトとデータベースのマッピングを行うRubyの有名なライブラリ。 この場合のtitleメソッドのようなアクセサメソッドも、ActiveRecordがデータベースのスキーマから名前を取得して自動的にメソッドを定義してくれる。
例2) オープンクラスとモンキーパッチ
アルファベットとスペースを残して、?!といった特殊文字列を削除する機能をもったプログラムを考える。
def to_alphanumeric(s) s.gsub /[^\w\s]/, "" end
しかしながら、メタプロマスターに言わせれば、「このメソッドはオブジェクト指向的じゃないね」。
class String def to_alphanumeric gsub /[^\w\s]/, "" end end
メタプロマスター的回答は、標準のStringクラスをオープンにし、そこにメソッドを加えること。 to_alphanumericはすべての文字列が持っていて問題のない汎用的な機能であったため、このような形を取ったのだ。
「クラスを定義するコード」は何だか特別な感じがするが、その他のコードと実際違いはない。
たとえば
3.times do class C puts "hello" end end #=> hello hello hello
ということもできる。 最初の1回の呼び出しえクラスが定義され、はじめてCが存在する。2,3回目は既存のクラスを再オープンしている。 我々は既存のクラスをいつでも再オープンして、修正できる。それには既存のクラス(StringやArrayといった)も含まれる。 この技術をオープンクラスという。 gemライブラリではオープンクラスがよく使われる。
オープンクラスの問題点:モンキーパッチ
うっかり、既存クラスに新しいメソッドを追加したつもりが、すでに定義されていたメソッドと同名にしてしまい、元のメソッドに依存しているために動作が変になるということがある。このようなクラスへの安易なパッチは「モンキーパッチ」という蔑称で呼ばれる。
自分の新しく定義しようとしているメソッドが存在するか確認するためには、たとえばArrayクラスなら
[].methods.grep /^method_name/
というように、methodsでメソッド一覧を取得して、サーチすると良い。
クラスの真実
オブジェクト、クラス、定数に関する説明をする章。
インスタンス変数
class MyClass def my_method @v = 1 end end obj = MyClass.new obj.class #=> MyClass
この時点では、@vは存在しない。
obj.my_method obj.instance_variables #=> [:@v]
my_method()を呼び出すと、存在しだす。
rubyインタプリタでinstance_variablesメソッドで調べて確認してみる。
メソッド
ほとんどのオブジェクトはObjectクラスから多くのメソッドを継承しているため、メソッド一覧はとても長いものになる、以下のように。
※ 以下に、私がQiitaにrubyのオブジェクト指向をちょっとキャピキャピしたテンションでまとめた記事があるので、良かったらよんでください。
メソッドの名称を区分する:インスタンスメソッド
MyClass.my_methodではなく、MyClass.new.my_methodであること(オブジェクトに対して利用するメソッドであること)を伝えるため、 前者をクラスメソッド、後者を 「インスタンスメソッド」 と呼ぶ。
インスタンス変数はオブジェクトに住んでおり、オブジェクトごとに値が変わりうる点に注意。
クラスだってオブジェクトである
オブジェクトと同じように、クラスにもクラスがある。
"hello, I'am string".class #=> String String.class #=> Class
といったように。 クラスのメソッドはClassクラス(ややこしい!)のインスタンスメソッドでもあるということ。 superclassメソッドでそのクラスのスーパークラスを知ることが出来る。
String.superclass #=> Object Numeric.superclass #=> Object Object.surperclass #=> BasicObject BasicObject.surperclass #=> nil
すべてのクラスはObjectクラスを継承しており、ObjectクラスはBasicObjectクラスを継承している。 そしてBasicObjectクラスがクラス階層のルートであることがわかった。
結局、クラスとは、new(), allocate(), superclass() メソッドを追加したモジュールに過ぎないのである(※allocateとは、newメソッドを補助するもので新しいオブジェクトを作る。newメソッドとは異なり、initializeメソッドの呼び出しは行わない)。
モジュールとクラス、なんで2種類あるわけ? 一緒にすればよかったんじゃないの?
ここまでで、「なぜじゃあモジュールとクラスが二つ用意されているのか」疑問に思う人もいるかもしれない(思った。)
メインの理由は
使い分けして意図を明確にできること
- モジュール: ネームスペース、インクルードされるだけのコード
- クラス: インスタンスを生成したり継承したりするもの
らしい。
定数
大文字で始まる参照は全て定数である。クラス名やモジュール名も含む。
定数はファイル、モジュール(or クラス)はディレクトリのようなもので、ファイルシステムと同じように、ディレクトリが違えば同じ名前のファイル(定数)を複数持てる。
module M X = "定数その1" class C X = "定数その2" end end M::X #=> 定数その1 M::C::X #=> 定数その2
のように。
モジュールと同じ名前のクラス名は使えないため、クラス名がモジュールと衝突したら、そのクラスを別のモジュール名で挟んむ(=ネームスペースで囲む)などの対応を取る。