Ruby の落とし穴 3


tech addict - ruby gotchas and caveats に触発されたわけでもないでしょうが、タイミング良く ruby-talk でも Ruby のクラス変数に混乱させられた話が。さらにこのスレを受けて、オライリーRuby ブログにもに NubyGems: Don't Use Class Variables! - O'Reilly Ruby というエントリーがあったりしたので、クラス変数の「落とし穴」について改めて。


クラス変数は、Smalltalk でも Smalltalk-80 の時代からすでにビミョ〜な位置づけで、それが、変数宣言を排除した Ruby に取り込まれることでさらに奇っ怪な挙動をするものになってしまったという(便利だけれども)扱いは慎重にしないといけない代物です。とは言っても、 Smalltalk では「便利」のほうが勝っているせいか比較的頻繁に使われているようで、たとえば手元の Squeak システム(3.9)でざっと調べてみると、2000 強のクラスのうち、300 ものクラスがクラス変数を持っていることが分かります。

(Smalltalk allClasses count: [:class | class classVarNames notEmpty]) @ Smalltalk allClasses size
=> 301@2176


後述のサブクラスでの共有を加味すれば(ほぼすべてのクラスが半ば強制的に共有させられる Object に定義されたクラス変数を除外しても)クラス変数と関わりを持つクラスの数は半数近くまでふくれあがります。

| allClasses count |
allClasses := Smalltalk allClasses.
count := allClasses count: [:class |
   (class allClassVarNames difference: Object classVarNames) notEmpty].
^ count @ allClasses size
=> 971@2176


ま、そんなことはどーでもいいわけですが。はい。閑話休題


クラス変数には、その名前からしてすでに「落とし穴」が待ち受けています。SmalltalkRuby のように、クラスもまたオブジェクトである言語では、インスタンスが起動できる通常のメソッドと区別して、クラス向けのそれを「クラスメソッド」と呼ぶことがありますが、これにつられて、インスタンス変数のクラス版が…とか思ってしまうとドツボります(この典型例が Apple の開発者向けドキュメントに登場する「クラス変数」)。


「クラス変数」と言えば、本来、クラスとそれに属するインスタンスによって共有される(つまり、参照と代入が可能な)大域変数の類のことを指します。したがって、前述のオブジェクトとしてのクラスが有するインスタンス変数(「クラスインスタンス変数」と呼ぶ)とはまったく異質のもので四半世紀前に登場して以来このかた、両者は明確に区別されています(と偉そうに言いつつ、私自身はここ数年でやっと…w)。なお、SmalltalkRuby 1.8 では、サブクラス群とそのインスタンスたちにも(特別なことをして隠蔽しないかぎり)共有されます。

SqueakSmalltalk
Object subclass: #B
   instanceVariableNames: ''
   classVariableNames: 'Var'

B >> Var
   ^ Var

B >> Var: x
   Var := x

B class >> Var
   ^ Var

B class >> Var: x
   Var := x
B subclass: #D

D >> Var
   ^ Var

D >> Var: x
   Var := x

D class >> Var
   ^ Var

D class >> Var: x
   Var := x
B Var: 'B Var'.
{B Var. D Var. B new Var. D new Var}.   "=> #('B Var' 'B Var' 'B Var' 'B Var') "
D Var: 'D Var'.
{B Var. D Var. B new Var. D new Var}.   "=> #('D Var' 'D Var' 'D Var' 'D Var') "
B new Var: 'aB Var'.
{B Var. D Var. B new Var. D new Var}.   "=> #('aB Var' 'aB Var' 'aB Var' 'aB Var') "
D new Var: 'aD Var'.
{B Var. D Var. B new Var. D new Var}.   "=> #('aD Var' 'aD Var' 'aD Var' 'aD Var') "
Ruby 1.8
class B
  @@Var = "B.Var"
  def Var; @@Var end
  def self.Var; @@Var end
  def Var=(x); @@Var=x end
  def self.Var=(x); @@Var=x end
end

class D < B
  def Var; @@Var end
  def self.Var; @@Var end
  def Var=(x); @@Var=x end
  def self.Var=(x); @@Var=x end
end

p [B.Var, D.Var, B.new.Var, D.new.Var]   #=> ["B.Var", "B.Var", "B.Var", "B.Var"]
D.Var = "D.Var"
p [B.Var, D.Var, B.new.Var, D.new.Var]   #=> ["D.Var", "D.Var", "D.Var", "D.Var"]
B.new.Var = "aB.Var"
p [B.Var, D.Var, B.new.Var, D.new.Var]   #=> ["aB.Var", "aB.Var", "aB.Var", "aB.Var"]
D.new.Var = "aD.Var"
p [B.Var, D.Var, B.new.Var, D.new.Var]   #=> ["aD.Var", "aD.Var", "aD.Var", "aD.Var"]


Java ユーザーなら static 変数(ただし、非 final、非 private で and/or 再定義による隠蔽のない状態)を思い浮かべるとよいかもしれません。

class B { static String Var = "B.Var"; }

class D extends B { }

class Main {
   public static void main(String[] args) {

      System.out.println( B.Var );         //=> B.Var
      System.out.println( D.Var );         //=> B.Var
      System.out.println( new B().Var );   //=> B.Var
      System.out.println( new D().Var );   //=> B.Var

      D.Var = "D.Var";
      System.out.println( B.Var );         //=> D.Var
      System.out.println( D.Var );         //=> D.Var
      System.out.println( new B().Var );   //=> D.Var
      System.out.println( new D().Var );   //=> D.Var

      new B().Var = "aB.Var";
      System.out.println( B.Var );         //=> aB.Var
      System.out.println( D.Var );         //=> aB.Var
      System.out.println( new B().Var );   //=> aB.Var
      System.out.println( new D().Var );   //=> aB.Var

      new D().Var = "aD.Var";
      System.out.println( B.Var );         //=> aD.Var
      System.out.println( D.Var );         //=> aD.Var
      System.out.println( new B().Var );   //=> aD.Var
      System.out.println( new D().Var );   //=> aD.Var
   }
}

クラス変数が Ruby に導入されたのは 1.5.3 および 1.6 から( [ruby-list:22136] [ruby-list:24985]The Ruby Language FAQ: Classes and modules)で、それまでは、同じ「クラス変数」を意識しつつ、しかし書き換えを禁じたかたちで設けられた「クラス定数」にハッシュ(Smalltalk なら“辞書”)を持たせることを、クラス変数の代替えとして推奨していたようです( [ruby-list:12489])。


個人的には、なんで Ruby にはわざわざ「クラス定数」と「クラス変数」という似たような機能が用意されているのか不思議だったのですが、 [ruby-list:4345] を読んで謎が解けました。もとから、変数を宣言しない Ruby のスタイルは「クラス変数」と相性が悪いことは分かっていて、それで書き換えのできない定数にしたのに、結局その後「クラス変数」を追加することになって(当初想定された問題が解決されぬまま)現在に至っているわけですね。


冒頭リンク先で指摘されている Ruby 1.8 のクラス変数の「落とし穴」を抽出して列挙すると以下のようになりましょうか。

サブクラスで先に初期化してしまうとスーパークラスの同名クラス変数が隠蔽されてしまう

class B
  def self.Var; @@Var end
  def self.Var=(x); @@Var=x end
end

class D < B
  @@Var = "D.Var"
end

B.Var = "B.Var"
p [B.Var, D.Var]   #=> ["B.Var", "D.Var"]]


1.9 でクラス変数は、サブクラスで共有できない仕様に変更されたようです。

class B
  @@Var = "B.Var"
  def self.Var_of_B; @@Var end
  def self.Var_of_B=(x); @@Var=x end
end

class D < B
  @@Var = "D.Var"
  def self.Var_of_D; @@Var end
end

D.Var_of_B = "B.Var, again"
p [B.Var_of_B, D.Var_of_B, D.Var_of_D]

#1.8 => ["B.Var, again", "B.Var, again", "B.Var, again"]
#1.9 => ["B.Var, again", "B.Var, again", "D.Var"]

スーパークラスの同名クラス変数をそうと知らずにいじっていることがある

@@Var = "Object.Var"
class C1; @@Var="C.Var" end
p @@Var   #=> "C.Var"

トップレベルの特異クラス定義式では、注目するクラスのクラス変数にはアクセスできない

class C2
  @@Var = nil
  def self.Var; @@Var end
end

class << C2
  def Var=(x); @@Var=x end
end

C2.Var = "C.Var"
p C2.Var    #=> nil


特異クラス定義式をクラス定義式の中に書けば問題は起こらない。

class C3
  @@Var = nil

  class << self
    def Var; @@Var end
    def Var=(x); @@Var=x end
  end
end

C3.Var = "C.Var"
p C3.Var    #=> "C.Var"

ブロック付き *_eval 呼び出しでは、レシーバのクラス変数にアクセスできない

class C4; @@Var="C.Var" end
C4.instance_eval { p @@Var }   #=> NameError: uninitialized class variable @@Var in Object
C4.class_eval { p @@Var }   #=> NameError: uninitialized class variable @@Var in Object


文字列で渡せばよい。

class C4; @@Var="C.Var" end
C4.class_eval( "p @@Var" )  #=> "C.Var"
C4.instance_eval( "p @@Var" )  #=> "C.Var"

二番目以降は、そうとは知らずにクラス「Object」のクラス変数を定義してしまったり、それにアクセスしたりしてしまうことに起因するトラブルとして集約できそうです。