Ruby が人気のあるプログラミング言語である理由のひとつに、強力なメタプログラミングがあると思います。Ruby の本当の力を知るのは、このメタプログラミングを理解したときだとも思います。
本書は、Ruby の内部動作をきちんと説明した上で、メタプログラミングの説明をしてくれます。とても丁寧な説明で、少しでもプログラミングの知識があれば理解できる文章になっています。
プログラミング能力を高めるひとつの方法に、良いソースコードを読むことというのがあります。Ruby で書かれたフレームワークやライブラリのソースコードを読むと、なんでこんな動作になるんだろう?という疑問や、どうしてこんな風に書けるのだろう?という疑問が湧いてきます。
本書を読むと、そういった疑問の一つ一つが本当に紐を解くように理解できるようになります。Ruby の基礎を勉強した初級者が中級者になろうと思ったときに、ぜひ読むべき本だと思います。
本書のおぼえがきです。
本書のまとめ
クラスはオブジェクト
オブジェクトにクラスがあるように、クラスにもクラスがあります。クラスのクラスは Class クラスです。
インスタンス変数はインスタンス、メソッドはクラスの持ち物
インスタンス変数はインスタンスごとにもっていますが、メソッドはインスタンスのクラスに定義されます。
class キーワードはクラスを定義するのではなくクラスをオープンする
class キーワードはクラスをオープンします。既存のクラスに対して使えば再オープンできて、メソッドの追加やモンキーパッチができるし、新規のクラスに対して使えば、そのクラスを定義することになります。
カレントオブジェクトとカレントクラス
Ruby のプログラムは常にカレントオブジェクト self とカレントクラスを持っています。
self が何を指すかに注意する
Ruby はクラスもオブジェクトなので、class キーワードでクラスをオープンすると self はクラスを指し示します。
メソッドを定義するとカレントクラスのインスタンスメソッドになる
def で定義されたすべてのメソッドはカレントクラスのインスタンスメソッドになります。
また、メソッドはインスタンスメソッドになるので、メソッドの中では self はメソッドを呼び出したインスタンス自身を指すことになります。
クラスのインスタンスの特異メソッドとクラスメソッドは同じ仕組み
クラスのインスタンスに特異メソッドを定義するには、def obj.my_method のようにします。
クラスもオブジェクトなので、同じように特異メソッドを定義できます。def MyClass.my_method というのは MyClass で指し示しているオブジェクト(つまりクラス)に特異メソッドを定義していることになります。
class キーワードでクラスをオープンすると、クラス定義のスコープでは、self はカレントクラスを指すので、
class MyClass def self.my_method end end
と
def MyClass.my_method end
は同じことをしていることになります。
特異メソッドは特異クラスに定義される
メソッドを定義するとクラスのインスタンスメソッドになります。特異メソッドも例外ではなく、クラスのインスタンスメソッドとして定義されます。
では、特異メソッドはどのクラスのインスタンスメソッドになるかというと、特異メソッドは特異クラスという特別なクラスのインスタンスメソッドとして定義されます。
クラスの特異クラスにメソッドを定義することは、クラスメソッドを定義すること
クラスメソッドはクラスのインスタンスに特異メソッドを定義するのと同じ方法で定義されることは、上にもまとめました。
つまり、クラスメソッドはクラスの特異クラスにメソッドを定義することと同じなので、特異クラスにインスタンスメソッドを定義することはクラスメソッドを定義することになります。
class MyClass class << self def hello end end end
で、MyClass.hello が定義できます。
特異クラスのスコープに入るには class << obj 構文を使う
特異クラスのスコープに入るには class << obj 構文をつかいます。特異クラスはインスタンスをひとつしかもてません。そのため、シングルトンとも呼ばれます。
private メソッドのルール
private メソッドは明示的なレシーバをつけて呼び出すことができないというものです。
Ruby のルールでは他のオブジェクトのメソッドを呼び出すにはレシーバを明示しなければいけないので、private メソッドは他のオブジェクトからは呼び出せません。
オープンクラス
Ruby は、既存のクラスを再オープンしていつでもクラスを修正することができる、オープンクラスという技術があります。このオープンクラスを使って、既存のクラスのバグを修正したり、既存のクラスにメソッドを追加したりすることをモンキーパッチと呼びます。
オープンクラスの使い方
Ruby の class キーワードは、クラス宣言というよりもスコープ演算子のようなものである。もちろん、存在しないクラスは作成する。しかしそれは、副作用と言ってもいいかもしれない。class の主な仕事は、あなたをクラスのコンテキストにつれていくことである。そのコンテキストであなたがメソッドを定義する。
Ruby でクラスの再オープンをするには、次のように通常のクラス宣言のように書きます。
class String
def hoge
# 追加したいメソッドの中身
end
def replace # <-- すでに String クラスに定義されている
# モンキーパッチしたい中身
end
end
1行目で String クラスを再オープンして、 2行目で hoge メソッドを String クラスに追加しています。また、6行目で既存のメソッド replace を再オープンしてモンキーパッチしています。
インスタンス変数、メソッド、クラス、定数、モジュール
インスタンス変数
インスタンス変数とは、クラスのインスタンスがもつ変数のことです。オブジェクトに対して、instance_variables() を呼び出すことで、そのオブジェクトが持つインスタンス変数の一覧を取得できます。インスタンス変数はインスタンスに所属します。
メソッド
メソッドには、インスタンスメソッドとクラスメソッドがあります。Ruby ではメソッドはクラスに所属します。そのため、クラスには Object.instance_methods と Object.methods という2つのメソッドが用意されています。
String.instance_methods == "hoge".methods # true
メソッドはクラスに所属するのでインスタンス間で共有されますが、インスタンス変数はインスタンスごとに割り当てられるため値がごっちゃになることはありません。
クラス
クラスはオブジェクトである。だから、オブジェクトに当てはまるものはクラスにも当てはまる。オブジェクトと同じように、クラスにもクラスがある。クラスは Class クラスのインスタンスなのだ。
"hello".class # String
String.class # Class
クラスはオブジェクトで、クラス名は定数です。
定数
Ruby では大文字で始まる参照は、クラス名やモジュール名も含めて、すべて定数である。定数のスコープには、変数のスコープとは異なるルールがある。
定数はディレクトリのようにツリー上にならんでいて、モジュールを使うことで MyModule::MyClass::MyConst のように書くことができるようになります。このように使うモジュールのことをネームスペースと呼んでいます。
既存のクラスやモジュール名と同名の参照を定義するとオープンクラスになってしまうので、ネームスペースを使って自分のクラスを判別できるようにすると行儀の良いプログラムになります。
メソッド探索
メソッドを呼び出すことは、レシーバに対してメッセージを送ることと言われます。レシーバとは、メソッドを呼び出したオブジェクトのことです。Ruby はメソッドを呼び出すと次の二つのことを行ないます。
- 呼び出すメソッドを探す。これをメソッド探索と呼びます。
- メソッドを呼び出す。これには self が必要になります。
Ruby はメソッドを呼び出す際に、レシーバの継承チェーンをたどってメソッドを見つけ出します。継承チェーンをたどる、とはクラスの親クラスを上にたどっていくことです。
モジュールはクラスをラップする
モジュールをクラスに(あるいは別のモジュールに)インクルードするとき、Ruby はちょっとしたトリックを使う。無名クラスを作ってモジュールをラップし、継承チェーンに挿入するのだ。それはインクルードするクラスの真上に入る。
module M
def my_method
"M#my_method()"
end
end
class C
include M
end
class D < C; end
D.ancestors # => [D, C, M, Object, Kernel, BasicObject]
Ruby には print() のようなどこからでも呼び出せるメソッドがあります。これは、Kernel モジュールのプライベートメソッドとして定義されているからです。
Ruby はすべてのクラスのスーパークラスとして Object クラスをセットします。この Object クラスの中で Kernel モジュールがインクルードされているため、すべてのクラスの中で Kernel メソッドのプライベートメソッドが呼び出せるのです。
これを利用すると、Kernel モジュールを再オープンしてメソッドを追加すれば、どこからでも呼び出せるメソッドが定義できます。
self
Ruby のコードはオブジェクト(カレントオブジェクト)の内部で実行される。カレントオブジェクトは self とも呼ばれる。self キーワードでアクセスできるからだ。
self の役割を担うオブジェクトは同時には複数存在しない。長時間その役割を担うオブジェクトも存在しない。メソッドを呼び出すときは、レシーバが self になる。その時点で、すべてのインスタンス変数は self のインスタンス変数になる。レシーバを明示せずにメソッドを呼び出すと、すべて self に対するメソッド呼び出しになる。他のオブジェクトを明示してメソッドを呼び出すと、そのオブジェクトが self になる。
メソッドを呼び出さない時の self 、つまりトップレベルコンテキスト(スクリプトが実行されたときの一番最初のスコープ)では、self は main という Ruby インタプリタが作成したオブジェクトの内部にいます。
private メソッドのルール
Ruby の private メソッドは他の言語と少しルールが違います。Ruby の private メソッドは次の二つのルールにしたがって実行されます。
- Ruby は、自分以外のオブジェクトのメソッドを呼び出すには、レシーバを明示的に指定する必要がある。
- private のついたメソッドを呼び出すときは、レシーバを指定できない。
つまり、private メソッドを呼び出すには、自分自身(self)にしかできないことになります。
厳密には、Ruby1.9 以前は send メソッド, Ruby1.9 からは instance_eval を使えば呼び出すことができます。
class MyClass
private
def hoge
"private called"
end
end
obj = MyClass.new
obj.send(:hoge) # <-- Ruby1.8
obj.instance_eval {|o| hoge } # <-- Ruby1.9
動的ディスパッチ
Ruby は、send() メソッドを使って呼び出したいメソッドをコードの実行時に決めることができます。これを 動的ディスパッチと呼びます。send() メソッドにはシンボルを渡すことが適当とされています。
シンボルと文字列の使い分けは、基本的には慣習なのである。多くの場合、シンボルは「物」の名前に使う。特にメタプログラミングに関するメソッドのような物である。
メソッドを動的に生成する
Ruby はメソッド呼び出しを動的に決めることができることの他に、メソッドを動的に定義することができます。メソッドを定義するには、Module#define_method() を使います。
class MyClass
define_method :myMethod do |args|
args * 5
end
end
define_method をつかった例を本書から抜粋し次に示します。
メソッドを動的に生成する - 本書P.81
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def self.define_component(name)
define_method(name) {
info = @data_source.send "get_#{name}_info", @id
price = @data_source.send "get_#{name}_price", @id
result = "#{name.to_s.capitalize}: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
}
end
define_component :mouse
define_component :cpu
define_component :keyboard
end
method_missing
Ruby はレシーバに対して呼び出されたメソッドを継承チェーンをたどって探します。この時、Object や BasicObject までたどってもメソッド定義が見つからない場合、Ruby インタプリタは method_missing を呼び出します。
動的ディスパッチでは呼び出すメソッド名を動的に作り出し、send() メソッドで呼び出していましたが、method_missing() をオーバーライドすることで、呼び出したいメソッドが定義されていない場合の動作を変更して、あたかもそこにメソッドがあるかのように振舞うことができます。
responde_to? のオーバーライド
method_missing の中で呼び出したメソッドは、ゴーストメソッド と呼ばれ、実際のメソッド定義はそこにはないものとして扱われます。しかし、これでは、Object#methods の一覧に含まれません。そこで、ゴーストメソッドがあたかもそこに本当にあるかのように振舞うように、respond_to? をオーバーライドするようにします。
class Computer
def respond_to?(method)
@data_source.respond_to?("get_#{method}_info") || super
end
#...
end
ブランクスレート
動的ディスパッチやゴーストメソッドであたかもそこにメソッドがあるかのように振舞うことができますが、同名のメソッドが既存のクラスに存在するような場合は、既存のクラスのメソッドが優先的に呼び出されてしまいます。せっかく動的ディスパッチやゴーストメソッドを意図していたのに、同名のメソッドがあることによりこの前提が崩れてしまうような場合、Module#undef_method() または、Module#remove_method() を使って既存のメソッドを削除することができます。
必要なメソッド以外何も無いことを、ブランクスレート(blank slate)、白紙の状態といいます。Ruby1.9 では BasicObject を継承することでブランクスレートなクラスをつくることができます。
Module#undef_method と Module#remove_method
undef_method は継承したメソッドも含めてすべてのメソッドを削除します。remove_method はレシーバのメソッドは削除しますが、継承したメソッドはそのまま残ります。
__で始まるメソッドは削除してはいけない
Ruby には で始まるメソッドが用意されています。例えば、send() メソッドに対して send() メソッド、id() メソッドに対して id() メソッドです。この で始まるメソッドは Ruby 内部から使われるメソッドとして予約されているため、undef_method や remove_method で削除するべきではありません。
スコープ
ブロック
ブロックは単体では実行できない。コードを実行するには、ローカル変数、インスタンス変数、self といった環境が必要になる。これらはオブジェクトにひもづけられた名前のことで、束縛とも呼ばれる。ブロックとは、これらをまとめて実行するためのものである。ブロックはコードと束縛の集まりで構成される。
ブロックを定義すると、そのとき、その場所にある束縛(binding)を取得できます。ブロックを実行する時、この束縛も一緒にメソッドに渡されます。これにより、メソッドを実行するスコープでブロックを呼び出す変数を宣言することや設定することができるようになります。
ブロックの変数スコープの例
下の例では、メソッド定義の中で変数 msg を定義していますがメソッド定義の内部の msg は使われません。メソッドの呼び出し時にブロックを定義した際に束縛されたローカル変数 msg が使われます。
def greet msg = "Hi " p yield("hamasyou") end msg = "Hello" greet {|name| "#{msg} #{name}" }
ローカル変数のスコープ
Ruby には Java や C# のような内部のスコープから外部のスコープの変数を参照できる入れ子は存在しません。外部のスコープと内部のスコープは区別されています。
変数の束縛が切り替わる例
v1 = 1 class MyClass v2 = 2 local_variables def my_method v3 = 3 local_variables end local_variables end # => [:v2] obj = MyClass.new obj.my_method # => [:v3] obj.my_method # => [:v3] local_variables # => [:obj, :v1, :_]
トップレベルコンテキストで定義した v1 変数が MyClass の中で見えなくなっています。また、MyClass のクラススコープで定義した v2 も、my_method の中で見えなくなっています。
スコープゲート
プログラムがスコープを切り替える場所は次の3つあります。
- クラス定義(class)
- モジュール定義(module)
- メソッド呼び出し(def)
これらのスコープゲートを超えて束縛をわたすには、キーワードではなくメソッドを使います。
class => Class.new module => Module.new def => Module#define_method()
my_var = "Hello" MyClass = Class.new do p "#{my_var}" define_method :my_method do p "#{my_var}" end end
レキシカルスコープを入れ子で参照できるようにすることを、フラットスコープと呼びます。
instance_eval()
Object#instance_eval() を使うと、オブジェクトのコンテキストでブロックを評価することができます。instance_eval() はフラットスコープで評価されるので、ローカル変数にアクセスすることができます。
my_var = 13 obj.instance_eval { @v = my_var } obj.instance_eval { @v } # => 13
クラス定義
メソッド定義の意味
Ruby のプログラムは常にカレントオブジェクト self と、カレントクラスを持っています。そして、メソッドを定義すると、カレントクラスのインスタンスメソッドになります。
プログラム中で、カレントオブジェクトを参照するには self を使いますが、カレントクラスを参照するキーワードはありません。
では、どうやってカレントクラスを判断すればよいかというと、class キーワードを見ればよいです。(目視で確認ですw)
class でオープンされたカレントクラスにメソッドを定義すると、そのカレントクラスのインスタンスメソッドを定義することになります。
カレントクラス
既存のクラスのコンテキストでブロックを評価する Object#class_eval というメソッドがあります。これを使ってクラスのオープンをすることもできます。
class キーワードを使ってクラスをオープンするにはクラス名が必要ですが、class_eval() は変数としてクラスを受け取った場合にも使うことができます。
class MyClass; end def add_hello_to(a_class) a_class.class_eval do def hello; "Hello" end end end add_hello_to MyClass obj = MyClass.new obj.hello # => "Hello"
class_eval を使うと、self とカレントクラスに変更が加えられます。instance_eval は self のみに変更を加えます。
instance_eval() と class_eval() はどちらを使う?
「このオブジェクトをオープンしたいが、クラスのことは気にしない」ならば、instance_eval() がいい。「ここでオープンクラスを使いたい」ならば、class_eval() がいい。
クラスインスタンス変数とクラス変数
クラスインスタンス変数とは、クラスのインスタンス変数のことでクラスに属しています。
class MyClass @my_var = 1 end
クラスインスタンス変数は、クラスのオブジェクトのインスタンス変数とは別物です。
class MyClass @v = 1 def my_method @v = 2 end def self.my_method @v end end MyClass.my_method # => 1 obj = MyClass.new obj.my_method # => 2 MyClass.my_method # => 1
クラス変数とは、@@プレフィックスを付けた変数で、クラスの階層に属しています。
class C @@v = 1 end class D < C def my_method @@v end end D.new.my_method # => 1
クラス変数は、サブクラスからもアクセスできます。
特異メソッド
Ruby では、特定のオブジェクトにのみメソッドを追加することができます。これを特異メソッドと呼びます。
msg = "hello, my name is hamasyou" def msg.hello self.upcase end msg.hello # => "HELLO, MY NAME IS HAMASYOU"
特異クラスのスコープ
Ruby には class キーワードを使った特別な構文があり、これを使うことで特異クラスのスコープに入ることができます。
class << an_object # 特異スコープの中 end
クラスメソッドの構文
クラスメソッドは実は、クラスの特異メソッドとなっています。つまり、クラスメソッドを定義するには、次のいずれかの方法を使うことができます。
class MyClass def self.my_method; end end def MyClass.my_method; end class MyClass class << self def my_method; end end end
インスタンスに特異メソッドを動的に追加するには特異クラスにメソッドを定義する
メソッドはクラスの持ち物なので、特異メソッドを動的にインスタンスに定義するにはインスタンスの特異クラスに対してメソッドを定義してやる必要があります。
特異メソッドを動的に定義するのに、instance_eval() メソッドを使うことができます。
じつは、instance_eval() メソッドを使うとレシーバのカレントクラスを特異クラスに変更しているのです。instance_eval() メソッドを使って特異クラスをオープンすることで、特異クラスに対してメソッドを定義することができるようになります。
class MyClass end obj = MyClass.new obj.instance_eval("def hello; 'Hello World'; end") obj.hello # => "Hello World" obj2 = MyClass.new obj2.hello # => NoMethodError: undefined method `hello' for #<MyClass:0x0xxxxx>
または、特異クラスのオブジェクトを class << obj 構文を使って取得し、そのオブジェクトに対して define_method でメソッドを定義します。
class MyClass end obj = MyClass.new singleton = class << obj; self; end singleton.send(:define_method, :hello) { "Hello World" } obj.hello # => "Hello World"
モジュールをインクルードしてクラスメソッドを定義する
「来る日も来る日も世界のどこかで、Rubyプログラマがモジュールをインクルードしてクラスメソッドを定義しょうとしている。私もかつてはそうだった。しかし、これではうまくいかない。」
モジュールの中にクラスメソッドを定義して、モジュールをインクルードしたクラスにだけクラスメソッドを定義したいという場合、下のようにしがちですがこれは間違った例です。
モジュールをインクルードしてクラスメソッドを定義する間違った例
module MyModule def self.my_method; "Hello"; end end class MyClass include MyModule end MyClass.my_method # => NoMethodError: undefined method `my_method' for MyClass:Class
モジュールをインクルードすると、モジュールのインスタンスメソッドが手に入ります。クラスメソッドではありません。すなわち、モジュールをインクルードしてクラスメソッドを手に入れるためには、モジュールにはインスタンスメソッドとして定義して、include する場所をクラスメソッドが定義される場所(つまり、特異クラス)で行うようにします。
module MyModule def my_method; "Hello"; end end class MyClass class << self include MyModule end end MyClass.my_method # => "Hello"
クラスメソッドは特異クラスのメソッドなので、特異クラスにメソッドを定義すればクラスメソッドになります。
class MyClass class << self def my_method; end end end
モジュールを特異クラスのメソッドに追加する方法、モジュールをオブジェクトに追加して特異メソッドとする方法は、Ruby がそのためのメソッドを用意しています。それが Object#extend() です。
module MyModule def my_method; "Hello"; end end obj = Object.new obj.extend MyModule obj.my_method # => "Hello" class MyClass extend MyModule end MyClass.my_method # => "Hello"
エイリアス
alias キーワードを使うと、Ruby のメソッドにエイリアスをつけることができます。
alias :new_method :old_method
alias された時点で、Ruby は古いメソッドを新しいメソッドとして再定義(コピー)します。したがって、次のような動作となります。
class String alias :real_length :length def length real_length > 5 ? "long" : "short" end end "War and Peace".length # => "long" "War and Peace".real_length # => 13
オープンクラスを使って、length() メソッドを再定義していますが、alias を使って real_length として再定義(コピー)しているので、real_length を呼び出すことで元の動作を呼び出すことができています。
Kernel#eval
eval() メソッドは引数で指定されたコード文字列(Ruby のコード)を処理します。
array = [10, 20] element = 30 eval("array << element") # => [10, 20, 30]
eval()、instance_eval()、class_eval() に渡すコード文字列の中は、ローカル変数にアクセスできます。
array = ["a", "b", "c"] x = "x" array.instance_eval "self[1] = x" array # => ["a", "x", "c"]
Binding オブジェクト
Binding はスコープをオブジェクトにまとめたものです。eval()、instance_eval()、class_eval() に Binding を渡すことでそのスコープでコードを実行できます。
class MyClass def my_method @x = 1 binding end end b = MyClass.new.my_method eval "@x", b # => 1
フックメソッド
Ruby には様々なイベントへのフックメソッドが用意されています。よく使われるのが、Module#included() で、モジュールが include された時に呼び出されます。次のようにすることで、Module で定義されたインスタンスメソッドをクラスメソッドに追加することができます。
module MyMixin def self.included(base) base.extend(MyClassMethods) end module MyClassMethods def hoge "hoge()" end end end class MyClass include MyMixin end MyClass.hoge # => "hoge()"