特異クラス定義の文法のナゾ、と Squeak の“なんちゃって特異クラス”


rubyco(るびこ)の日記 - 疑問:特異クラスの構文の不思議 発で、サブクラス定義時は class NewSub < Super; end なのに、特異クラス定義は(特異クラスを仮に ε として省略しなかった場合)class ε << obj; end とする積極的な理由が見つけられない…というお話。


調べてみると Matz さんの発言で件の構文が登場するのは [ruby-list:4677] が比較的古いもののようですが、これ(と、一連のやりとりの中)にはなぜ << なのか?という rubyco さんのような疑問に答える情報を見つけることはできませんでした。



私は rubyco さんの疑問を見るまでは、class NewSub < Super; end を「既存の Super に注目して(そのサブクラスの…)新しい NewSub を」という解釈でいたので、class 無名 << obj; end も「既存の obj に注目して(そのクラスの…)無名クラスを」…だからこれでいいのか、というふうに自分の中では解決してしまっていました。つまり、< や << の開いているほうが(継承関係やクラス-インスンタス関係の“上”とは関係なく)“既存のもの”、あるいは、新しいクラスを作るための“とっかかり”、“拠り所”といったニュアンスでとらえたわけです。



しかし、rubyco さんの提案を見ているうちに、それなら右側に追記したものを“拠り所”と解釈すればよいのであって、その間に挟む < や << の形は関係の上下を明示的にするのに使ってしまってもよいのではかなぁ…とも思い始めました。そう考えはじめると、にわかに rubyco さんの class >> obj のほうが自然な記述に見えてくるから不思議です。



ちなみに Smalltalk ではどうかというと(これを書かないと気が済まないので…スミマセン)、まず、よく知られているようにサブクラスはスーパクラス(この場合 Super)への subclass: #NewSub instanceVariableNames: ... という長いメッセージ送信で表現されます(「(原則として)何でもメッセージング…」というドグマ)。この際、新しいサブクラスに付ける名前は、このメッセージ中の第一パラメータのシンボル #NewSub として与えます。

Super
   subclass: #NewSub
   instanceVariableNames: 'instVar1 instVar2'
   classVariableNames: 'ClassVar1 ClassVar2'
   poolDictionaries: 'PoolVarsDict'
   category: 'Category-Name'


また、一般に Smalltalk には Ruby の特異クラスに相当する機構はないのですが、SqueakSmalltalk に限っては eToys でインスタンスベース的機能が必要になる都合で、特別にインスンタス特異的なクラスを作る #assureUniClass というメソッドが標準で用意されています。それを使うと、こんな振る舞いをさせられます。

| obj1 obj2 |

Object compile: 'foo ^#foo'.      " Object に、メソッド #foo を定義 "

obj1 := Object new.               " Object のインスタンス作成 "

obj1 assureUniClass.              " obj1 になんちゃって特異クラスを定義 "
obj1 class compile: 'bar ^#bar'.  " obj1 のなんちゃって特異クラスに、メソッド #bar を定義 "

obj2 := Object new.               " 別の Object のインスタンスを作成 "

{obj1 foo. obj1 bar. obj2 foo}.   " => #(#foo #bar #foo) "
obj2 bar                          " error: MessageNotUnderstood: Object>>bar "


これを Ruby で書くと、おおよそこんな感じ。

class Object; def foo; :foo end end

obj1 = Object.new

class << obj1; def bar; :bar end end

obj2 = Object.new

[obj1.foo, obj1.bar, obj2.foo]     #=> [:foo, :bar, :foo]
obj2.bar                           #=> NoMethodError: undefined method `bar'