DSLを作ったりするために、Rubyの型システムについて整理してみます。Ruby 1.8ベースです。
Rubyはクラスベース (class-based) のオブジェクト指向言語です。
クラスを雛形として、クラスからオブジェクトを生成します。メジャーなプログラミング言語ではよくあるやり方です。
クラスベースでないのは、プロトタイプベース (prototype-based) と呼ばれ、あるオブジェクトからコピーして別のオブジェクトを生成します。
Rubyはクラスもオブジェクトなのが変わっています。例えば、メソッド呼び出しの引数として、オブジェクトと同じ形でクラスを渡すこともできます。
JavaやC++では、クラスはオブジェクトではないので、このようなことはできません。
クラスは次のようにして定義します。
class クラス名 < スーパークラス名 クラス定義... end
「< スーパークラス名」を省略すると、自動的に Object
のサブクラスになります。スーパークラスを持たないクラスを新しく作ることはできません。ただし,
Object
クラスにはスーパークラスはありません。
(2019.9) Ruby 2.5では, Object
のスーパークラスは BasicObject
クラスであり, BasicObject
クラスはスーパークラスがありません。
クラスからオブジェクトを生成することをインスタンス化といい、そうやって生成したオブジェクトのことを、クラスと対比して「インスタンス」と呼ぶこともあります。
Rubyではクラスは Class
クラスのインスタンスです。次のようにしてもクラスを生成できます。
クラス名 = Class.new(スーパークラス名)
クラスに変わりないので、このようにして作ったクラスでも普通にインスタンス化することもできます。次の例では, N はクラスです。
逆に, オブジェクト (インスタンス) からは#class
メソッドでクラスオブジェクトを得ることができます。
クラスもオブジェクトなので, class
メソッドを持っています。クラスのclassメソッドはClassクラスを返します。Classクラス (これもオブジェクト) のクラスはClassクラスで、ここでループしています。
[[図1]]
オブジェクトはメソッドを持てます。def
... end
でメソッドを定義します。
Ruby では, 見た目, インスタンスメソッドとクラスメソッドがあります。インスタンスメソッドはインスタンスに対して呼び出すようなメソッド, クラスメソッドはクラスに対して呼び出すようなメソッドです。
Rubyでは、クラス定義のなかでも普通に文が書け、クラス定義のときに実行されます。
さらに, Rubyでは, クラスを通じてではなく, 一つ一つのオブジェクトに直接メソッドを定義するようにも書けます。
実際には, クラスメソッドは, クラスのクラスのインスタンスメソッドです。
さらに, オブジェクトのメソッドは, 内部でこっそり無名クラスが作られた上で, その無名クラスのインスタンスメソッドになります。オブジェクトのクラスが差し替えられます。(特異クラス; 後述)
def
... end
の内側では, 定義したときのではなく, メソッドが呼び出されたときのレシーバ (上記の例では変数 iが指すオブジェクト) がself
になります。
インスタンスメソッドは、インスタンス化したオブジェクトに付けるためのメソッドで、インスタンス化して初めて呼び出せるようになります。
クラスメソッドは、クラスというオブジェクトに付けるメソッドなので、次のように呼び出します。
Rubyでは、既存のクラスに後からメソッドを追加できます。例として、Classクラスにインスタンスメソッドを追加してみます。
オブジェクトのメンバの変数をインスタンス変数といいます。Rubyでは、JavaやC++と違い、オブジェクトのメンバをあらかじめ宣言する必要はありません。
インスタンス変数は, インスタンスごとに格納されます。たとえクラスが同じでも, インスタンス間で共有されません。
インスタンス変数は「@変数名」と書き、実行時の self
が指すオブジェクトに格納されます。
この例では、f()
, g()
が呼び出されるときのself
はC
なので、@u
は g()
のなかからアクセスできます。(継承した場合は後述)
同じ「@v
」と書いていても、s()
はインスタンスメソッドなので、呼び出されるときのレシーバ (= self
) は、上の例では, iが指すオブジェクトになります。
注意したいのは, Rubyでは実行時にインスタンス変数が確保されるため, 継承 (すぐ後で説明。) との関係で, スーパークラスで定義したインスタンス変数がサブクラスのインスタンスで何もせずに使えるわけではない, ということ。スーパークラスのメソッドを呼び出して, インスタンス変数を確保してやらなければならない。
[[TODO: 例]]
クラスは継承できます。
継承されるほうのクラスを基底クラスあるいはスーパークラス、継承するほうのクラスを派生クラスまたはサブクラスといいます。
クラスの継承は、メソッド呼び出しの探索経路にスーパークラスのメソッドを加える、という意味です。
上の例で, スーパークラスというオブジェクトとサブクラスというオブジェクトはあくまでも別のオブジェクトということに注意してください。インスタンスは, スーパークラスに基づく部分とサブクラスに基づく部分を両方含みます。
継承によって、スーパークラスオブジェクトがコピーされたりはしません。
インスタンス変数はオブジェクトに属しますから, 当然, クラスのインスタンス変数が混ざったりもしません。
クラスメソッドのなかでインスタンス変数を使ったときに、派生クラスのインスタンス変数にアクセスしてしまうのは、ちょっと字面から想像できません。メソッド定義のときにはself
= Aなのでなおさらです。
考えてみると、クラスというオブジェクトにメソッドを付けているような字面なのに、サブクラスをレシーバにしても呼び出せるのが違和感の元のように思います。
とはいうものの、この挙動は、実用的な利点もあります。
次の例は、MF Bliki: ClassInstanceVariable のサンプルを一部修正したものです。
これはスーパークラスでクラスメソッドを定義していますが、インスタンス変数はサブクラスのものを用います。
オブジェクトに直接メソッドを付けようとすると、内部でこっそり、そのオブジェクト専用の特異クラスが生成されます。
上の、違和感のあったクラスメソッドは、次のように考えればスッキリします。
1. クラスC
にクラスメソッドを付けようとすると、特異クラス (仮にMetaC
とします。) が生成され、C
はその特異クラスのインスタンスになります。
2. クラスD
の特異クラス (仮にMetaD
) のスーパークラスはMetaC
になります。
3. クラスC
はMetaC
のインスタンスであり、クラスD
はMetaD
のインスタンスなので、そのインスタンス変数はそれぞれ別のものです。
4. クラスD
をレシーバにしてクラスメソッドを呼び出す場合、MetaD
, MetaC
の順で検索されます (クラスのインスタンスメソッドをスーパークラスの方向へ辿る)。
特異クラスは、次の構文で明示的に定義できます。
class << オブジェクト ... end
Rubyでは、特異クラスすらオブジェクトです。
D
の特異クラスのスーパークラスはClass
の特異クラスになっています。惜しい。惜しすぎる。
[Note.] Ruby 1.9では、D
の特異クラスのスーパークラスはC
の特異クラスになっている、ようです。Rubyのメタクラス階層について再び - 世界線航跡蔵
「def オブジェクト.メソッド
」という書き方のメソッド定義のほうが一貫性がないような気がします。
クラスのインスタンス変数はそれはそれで意味がありますが、クラスのインスタンス全部で変数を共有したい場合もあります。
クラス変数は、クラス、そのサブクラス、それらのインスタンスで共有されます。
Rubyでは、メソッドや変数がどのオブジェクトのものか、という意識が強いのに、クラス変数は、あっさりオブジェクトを跨いでしまっています。むしろグローバル変数に近い。