Ruby の落とし穴 2


id:sumim:20070102:p1 の続きのような、そうでないような…。


Ruby の、いわゆる“インスタンスベース”(語弊があるので使うのを避けるようにしていますが、一般には「プロトタイプベース」)的な側面としてよく紹介されるものに、「特異メソッド」と呼ばれるインスタンス特異的メソッドがあります。しかし、じつのところ Ruby のそれは「特異クラス」という特殊な位置づけの匿名クラスにより、いたって“クラスベース”的な考え方により実現されているものであって、少なくとも仕組みとしては、インスタンスベースからはほど遠いところにある機能だ…というのは、インスタンスベースの考え方を理解した人向けには、ある種の「落とし穴」だと言えそうです。


前後しますが、クラスベースにはないインスタンスベースの本質は(よく言われるように、newを使わずcloneを使うとか、クラスの有り・無し、あるいは、継承ではなく委譲ベースだとかではなく…)、インスタンスがクラスを介さずに個別の変数やメソッドを持てるかどうか、に尽きます。そうした切り口では、Ruby において真にインスタンスベース的なものを挙げるとすれば、インスタンス変数の振る舞いのほうがずっとふさわしいでしょう。


SmalltalkJava などのクラスベースの言語における通常とは異なり、Rubyインスタンス変数は代入式がその宣言を兼ねるようです。つまり、すでに存在すればそれを使うし、なければ新たに作る…という振る舞いをします。このとき、すべてのインスタンス(必要ならすべてのサブクラスに属するインスタンス)に追加されるわけではなく、あくまで注目しているインスタンスのみに限られる…という点で“インスタンスベース”的であるわけです。


たとえば Ruby の場合、クラス Foo において、インスタンスが @var というインスタンス変数を持つことを期待して、それにアクセスするためのメソッドを定義したとしても、生成したての Foo のインスタンスはまだ @var という“スロット”を持っていません。くだんのアクセッサを介して @var にアクセス(厳密には参照するだけではダメで、代入)するアクションがあってはじめて @var というスロットが確保されます。

class Foo; attr_accessor :var end
foo1 = Foo.new
foo2 = Foo.new
p [foo1, foo2].collect { |foo| foo.instance_variables }   #=> [[], []]
foo1.var
p [foo1, foo2].collect { |foo| foo.instance_variables }   #=> [[], []]
foo1.var = :something
p [foo1, foo2].collect { |foo| foo.instance_variables }   #=> [["@var"], []]


ここで、知りたいインスタンス変数について *クラスに* ではなく、*インスタンスに* 尋ねているところがミソです。JavaSmalltalk などでは、インスタンスがクラスで宣言されたインスタンス変数のスロットを持つことは自明なので、こうした“問い合わせ”はふつうクラスに対して行なわれます。

Java の場合
import java.lang.reflect.Field;
import java.util.ArrayList;

class Foo { public int var; }

public class Main {
   public static void main(String[] args) {
      Field[] fields = Foo.class.getDeclaredFields();
      ArrayList<String> instVarNames = new ArrayList<String>();
      for (Field field : fields) { instVarNames.add(field.getName()); }
      System.out.println( instVarNames );
   }
}
//=> [var]
Smalltalk の場合
Object subclass: #Foo
   instanceVariableNames: 'var'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Category-Name'
Foo instVarNames   "=> #('var') "


じつのところ、Ruby のオブジェクトにインスタンス変数用のスロットを追加するのには、インスタンス変数へのアクセッサ(すなわち *クラスに* 定義されたメソッド)を介する必要すらなかったりもします。

obj1 = Object.new
obj2 = Object.new
p [obj1, obj2].collect { |foo| foo.instance_variables }   #=> [[], []]
obj1.instance_eval { @var = :something }
p [obj1, obj2].collect { |foo| foo.instance_variables }   #=> [["@var"], []]

つまり、Ruby において各々のインスタンスがどんなスロットを持っているのか、本来ならその管理責任者であってしかるべきクラスは、完全に“蚊帳の外”に置かれているわけです。このような Ruby におけるインスタンス変数のインスタンスベース的側面は、リフレクション機能に興味を持ちたての人には、これまた「落とし穴」になるのではないかな…と思いました。似たようなことは、Python にも言えそうです。

class Foo: pass

foo1 = Foo()
foo2 = Foo()
print [foo.__dict__.keys() for foo in [foo1, foo2]]   #=> [[], []]
foo1.var = "something"
print [foo.__dict__.keys() for foo in [foo1, foo2]]   #=> [['var'], []]


id:sumim:20070108:p1 に続くような続かんような…(^_^;)。