Rubyの型システム (第1回)

DSLを作ったりするために、Rubyの型システムについて整理してみます。Ruby 1.8ベースです。

Rubyのクラス

Rubyはクラスベース (class-based) のオブジェクト指向言語です。

クラスを雛形として、クラスからオブジェクトを生成します。メジャーなプログラミング言語ではよくあるやり方です。

クラスベースでないのは、プロトタイプベース (prototype-based) と呼ばれ、あるオブジェクトからコピーして別のオブジェクトを生成します。

Rubyはクラスもオブジェクトなのが変わっています。例えば、メソッド呼び出しの引数として、オブジェクトと同じ形でクラスを渡すこともできます。

JavaやC++では、クラスはオブジェクトではないので、このようなことはできません。

クラスとClassクラス

クラスは次のようにして定義します。

class クラス名 < スーパークラス名
  クラス定義...
end

「< スーパークラス名」を省略すると、自動的にObjectのサブクラスになります。スーパークラスを持たないクラスを新しく作ることはできません。ただし、Objectクラスにはスーパークラスはありません。

クラスからオブジェクトを生成することをインスタンス化といい、そうやって生成したオブジェクトのことを、クラスと対比して「インスタンス」と呼ぶこともあります。

RubyではクラスはClassクラスのインスタンスです。次のようにしてもクラスを生成できます。

クラス名 = Class.new(スーパークラス名)

クラスに変わりないので、このようにして作ったクラスでも普通にインスタンス化することもできます。次の例では, N はクラスです。

[POPUP]
  1. N = Class.new
  2. k = N.new
  3. p k #=> #<N:0x...>

逆に, オブジェクト (インスタンス) からはclassメソッドでクラスオブジェクトを得ることができます。

[POPUP]
  1. class C; end
  2. i = C.new
  3. p i #=> #<C:0x...>
  4. p i.class #=> C
  5. p i.class.class #=> Class
  6. p i.class.class.class #=> Class

クラスもオブジェクトなので、classメソッドを持っています。クラスのclassメソッドはClassクラスを返します。Classクラス (これもオブジェクト) のクラスはClassクラスで、ここでループしています。

[[図1]]

メソッド定義

オブジェクトはメソッドを持てます。def ... end でメソッドを定義します。

Ruby では, 見た目, インスタンスメソッドとクラスメソッドがあります。インスタンスメソッドはインスタンスに対して呼び出すようなメソッド, クラスメソッドはクラスに対して呼び出すようなメソッドです。

Rubyでは、クラス定義のなかでも普通に文が書け、クラス定義のときに実行されます。

[POPUP]
  1. class C
  2. # 文を書ける
  3. p self #=> C
  4. # クラスメソッド.「def C.f」と書いてもよい (self = Cなので).
  5. def self.f
  6. p self #=> この例ではC. ただし、継承すると違う場合あり
  7. end
  8. # インスタンスメソッド
  9. def g
  10. p self #=> #<C:0x...>
  11. end
  12. end

さらに, Rubyでは, クラスを通じてではなく, 一つ一つのオブジェクトに直接メソッドを定義するようにも書けます。

[POPUP]
  1. class C; end
  2. i = C.new
  3. # レシーバ.メソッド名
  4. def i.f
  5. p self #=> #<C:0x...>
  6. end
  7. i.f

実際には, クラスメソッドは, クラスのクラスのインスタンスメソッドです。

さらに, オブジェクトのメソッドは, 内部でこっそり無名クラスが作られた上で, その無名クラスのインスタンスメソッドになります。オブジェクトのクラスが差し替えられます。(特異クラス; 後述)

def ... end の内側では, 定義したときのではなく, メソッドが呼び出されたときのレシーバ (上記の例では変数 iが指すオブジェクト) がselfになります。

インスタンスメソッドは、インスタンス化したオブジェクトに付けるためのメソッドで、インスタンス化して初めて呼び出せるようになります。

[POPUP]
  1. i = C.new
  2. i.g

クラスメソッドは、クラスというオブジェクトに付けるメソッドなので、次のように呼び出します。

Rubyでは、既存のクラスに後からメソッドを追加できます。例として、Classクラスにインスタンスメソッドを追加してみます。

[POPUP]
  1. class Class
  2. def s; end
  3. end
  4. class T; end
  5. # TはClassクラスのインスタンス
  6. T.s
  7. # ClassはClassクラスのインスタンスでもある
  8. Class.s

インスタンス変数

オブジェクトのメンバの変数をインスタンス変数といいます。Rubyでは、JavaやC++と違い、オブジェクトのメンバをあらかじめ宣言する必要はありません。

インスタンス変数は, インスタンスごとに格納されます。たとえクラスが同じでも, インスタンス間で共有されません。

インスタンス変数は「@変数名」と書き、実行時の selfが指すオブジェクトに格納されます。

[POPUP]
  1. class C
  2. @u = 50
  3. def self.f
  4. # この例ではself = Cなので、Cのメンバ
  5. @v = 100
  6. end
  7. def self.g
  8. p "#{@v}, #{@u}"
  9. end
  10. # インスタンスメソッド
  11. def s
  12. @v = 500
  13. end
  14. end
  15. C.f
  16. i = C.new # iはCのインスタンス
  17. i.s
  18. C.g #=> 100, 50
  19. def i.t; p @v; end
  20. i.t #=> 500

この例では、f(), g() が呼び出されるときのselfCなので、@uは g() のなかからアクセスできます。(継承した場合は後述)

同じ「@v」と書いていても、s() はインスタンスメソッドなので、呼び出されるときのレシーバ (= self) は、上の例では, iが指すオブジェクトになります。

注意したいのは, Rubyでは実行時にインスタンス変数が確保されるため, 継承 (すぐ後で説明。) との関係で, スーパークラスで定義したインスタンス変数がサブクラスのインスタンスで何もせずに使えるわけではない, ということ。スーパークラスのメソッドを呼び出して, インスタンス変数を確保してやらなければならない。

[[TODO: 例]]

継承

クラスは継承できます。

継承されるほうのクラスを基底クラスあるいはスーパークラス、継承するほうのクラスを派生クラスまたはサブクラスといいます。

クラスの継承は、メソッド呼び出しの探索経路にスーパークラスのメソッドを加える、という意味です。

[POPUP]
  1. class B
  2. def f; p "B::f" end
  3. def f2; p "B::f2" end
  4. end
  5. # クラスの派生
  6. class D < B
  7. # 基底クラスのインスタンスメソッドを隠す
  8. def f; p "D::f"; end
  9. end
  10. class D2 < B
  11. def f; p "D2::f"; end
  12. end
  13. i = D.new
  14. i.f #=> D::f
  15. # メソッド検索にB (基底クラス) が加わるので、Bで定義したメソッドも呼び出せる
  16. i.f2 #=> B::f2
  17. # クラスオブジェクトはただ一つだけしか生成されず、派生するクラスで共有される
  18. p B.object_id #=> 70167453374980
  19. p D.object_id #=> 70167453374920
  20. p D.superclass #=> B
  21. p D.superclass.object_id #=> 70167453374980
  22. p D2.superclass.object_id #=> 70167453374980

上の例で, スーパークラスというオブジェクトとサブクラスというオブジェクトはあくまでも別のオブジェクトということに注意してください。インスタンスは, スーパークラスに基づく部分とサブクラスに基づく部分を両方含みます。

継承によって、スーパークラスオブジェクトがコピーされたりはしません。

インスタンス変数はオブジェクトに属しますから, 当然, クラスのインスタンス変数が混ざったりもしません。

[POPUP]
  1. class A
  2. @a = 10
  3. # 落とし穴:: 呼び出されるときのselfはAとは限らない
  4. def self.a; @a end
  5. def self.a_
  6. p self
  7. # Aだと明記すればAオブジェクトのインスタンス変数が取れる
  8. A.instance_variable_get('@a')
  9. end
  10. end
  11. class B < A
  12. @a = 20
  13. end
  14. class C < A
  15. @a = 5
  16. end
  17. class D < A; end
  18. # インスタンス変数は共有されない
  19. p A.a #=> 10
  20. p B.a #=> 20
  21. p C.a #=> 5
  22. # スーパークラスのインスタンス変数は見えない
  23. p D.a #=> nil
  24. p D.a_ #=> D 10

クラスメソッドのなかでインスタンス変数を使ったときに、派生クラスのインスタンス変数にアクセスしてしまうのは、ちょっと字面から想像できません。メソッド定義のときにはself = Aなのでなおさらです。

考えてみると、クラスというオブジェクトにメソッドを付けているような字面なのに、サブクラスをレシーバにしても呼び出せるのが違和感の元のように思います。

とはいうものの、この挙動は、実用的な利点もあります。

次の例は、MF Bliki: ClassInstanceVariable のサンプルを一部修正したものです。

これはスーパークラスでクラスメソッドを定義していますが、インスタンス変数はサブクラスのものを用います。

[POPUP]
  1. class Employee
  2. # 呼び出されるときは, selfはサブクラス
  3. def self.instances= x; @instances = x end
  4. def self.instances; @instances end
  5. def store
  6. # selfはサブクラスのインスタンス
  7. self.class.instances ||= []
  8. self.class.instances << self
  9. end
  10. def initialize name; @name = name end
  11. end
  12. class Overhead < Employee; end
  13. class Programmer < Employee; end
  14. Overhead.new('Martin').store
  15. Overhead.new('Roy').store
  16. Programmer.new('Erik').store
  17. puts Overhead.instances.size #=> 2
  18. puts Programmer.instances.size #=> 1

特異クラス

オブジェクトに直接メソッドを付けようとすると、内部でこっそり、そのオブジェクト専用の特異クラスが生成されます。

上の、違和感のあったクラスメソッドは、次のように考えればスッキリします。

1. クラスCにクラスメソッドを付けようとすると、特異クラス (仮にMetaCとします。) が生成され、Cはその特異クラスのインスタンスメソッドになります。

2. クラスDの特異クラス (仮にMetaD) のスーパークラスは、MetaCになります。

3. クラスCはMetaCのインスタンスであり、クラスDはMetaDのインスタンスなので、そのインスタンス変数はそれぞれ別のものです。

4. クラスDをレシーバにしてクラスメソッドを呼び出す場合、MetaD, MetaCの順で検索されます (クラスのインスタンスメソッドをスーパークラスの方向へ辿る)。

特異クラスは、次の構文で明示的に定義できます。

class << オブジェクト
  ...
end

Rubyでは、特異クラスすらオブジェクトです。

[POPUP]
  1. class C
  2. # selfはC
  3. class << self
  4. p self #=> #<Class:C>
  5. p self.object_id #=> 70240027190120
  6. # 特異クラスのインスタンスメソッド
  7. def f
  8. end
  9. end
  10. end
  11. class D < C
  12. class << self
  13. p "#{self}, #{self.object_id}" #=> #<Class:D>, 70240027190040
  14. p "#{self.superclass}, #{self.superclass.object_id}"
  15. #=> #<Class:Class>, 70240027262160
  16. p "#{self.class}, #{self.class.object_id}"
  17. #=> Class, 70240027262220
  18. end
  19. p self.class.object_id #=> 70240027262220
  20. end
  21. # クラスメソッドとして呼び出せる
  22. D.f

Dの特異クラスのスーパークラスはClassの特異クラスになっています。惜しい。惜しすぎる。

[Note.] Ruby 1.9では、Dの特異クラスのスーパークラスはCの特異クラスになっている、ようです。Rubyのメタクラス階層について再び - 世界線航跡蔵

def オブジェクト.メソッド」という書き方のメソッド定義のほうが一貫性がないような気がします。

クラス変数

クラスのインスタンス変数はそれはそれで意味がありますが、クラスのインスタンス全部で変数を共有したい場合もあります。

クラス変数は、クラス、そのサブクラス、それらのインスタンスで共有されます。

[POPUP]
  1. class C
  2. @@v = 1
  3. def self.cvar; @@v end
  4. def icvar; @@v end
  5. end
  6. class D < C
  7. @@v = 2
  8. end
  9. p C.cvar #=> 2
  10. p D.cvar #=> 2
  11. p C.send :class_variable_get, '@@v' #=> 2
  12. i = D.new
  13. p i.icvar #=> 2

Rubyでは、メソッドや変数がどのオブジェクトのものか、という意識が強いのに、クラス変数は、あっさりオブジェクトを跨いでしまっています。むしろグローバル変数に近い。