トレイトで、再定義時に遮蔽されてしまうメソッドを super へのメッセージングで呼び出せるようにする方法


Ruby のモジュールや Strongtalk のミックスインは、継承パスに挿入された抽象的なクラスのように振る舞うため、下位のクラスやモジュール(ミックスイン)でオーバーライドしたメソッドでも、 super(Strongtalk では super へのメッセージング)でコールできます。

Ruby のモジュールの場合
class B
  def hoge; "B" end
end

module M
  def hoge; super + " M" end
end

class C < B
  include M
  def hoge; super + " C" end
end

p C.new.hoge   #=> "B M C"
Strongtalk のミックスインの場合(Squeak Smalltalk 風に)
Object subclass: #B
B>>hoge   ^'B'

Mixin named: #M
M>>hoge   ^super hoge, ' M'

(M |> B) subclass: #C
C>>hoge   ^super hoge, ' C'

C new hoge   "=> 'B M C'"

「|>」は「▷」を連想させるオペレータで、指定したミックスイン(第一オペランド)を指定したクラス(第二オペランド)へ組み込むことを意味します。モジュールをサブクラスにインクルードするスタイルをとる Ruby と違って Strongtalk では、スーパークラス指定の際にミックスインの継承パスへの挿入も同時に行なうスタイルをとります。


一方、これらミックスインとは異なり、Squeak Smalltalk のトレイト(Traits)では、トレイトを use(Ruby 風に表現すればインクルード)しても、そのクラスにおける super の振る舞いは変わりません。したがって、クラスで同名のメソッドが再定義されると、use したトレイトの同名メソッドをコールする方法がなくなり、結果、それらは遮蔽されてしまいます。

Squeak Smalltalk のトレイトの場合
Object subclass: #B
B>>hoge   ^'B'

Trait named: #T
T>>hoge   ^super hoge, ' T'

Object subclass: #C uses: T

C new hoge   "=> 'B T' "
C>>hoge   ^super hoge, ' C'

C new hoge   "=> 'B C' "

(トレイトには Ruby 同様エイリアス(別名)機能があるので、遮蔽されるメソッドの別名を作ってアクセスの手段を確保する方法はありますが、それはまた別の話。為念)


では、Squeak Smalltalk のトレイト(Traits)で、Ruby のモジュールや Strongtalk のミックスインのような振る舞いを実現したい場合はどうするか…という話。前置きが長すぎ。w


いろいろと調べたり考えたりしてみたのですが、いわば抽象クラスであるモジュールやミックスインと違い、メソッドのコレクションにすぎないトレイトには上位クラスという概念も、自身がそれになる…という能力にも欠けているので、そもそも継承パスに参加するということ自体が不可能なようです。つまり「できない」というのが素直な答。

ただ、さいわい Ruby と違って Smalltalk のクラスは、スーパークラスをあとから変更できる動的性を有しているので、対象となるトレイトを use したクラス(仮に M )を新たに作成し、それを継承パスの間に挿入する操作(M を B のサブクラスに、C のスーパクラスを B から M に…)をしてやれば、とりあえず同様のことは可能であることがわかりました。

B subclass: #M uses: T
M subclass: #C uses: {}

C new hoge   "=> 'B T C' "

なんのことはない。話は非常に単純で、モジュール相当の抽象クラスを介し、include 相当のことをしてやれば済む話だったわけです(^_^;)。おかげで、自分の中でトレイトとミックスインの違いが明瞭になりました。


ところで「動的性」で、ふと、以前、Matzにっき のトレイトについてこんな記述

ただ、traitは「a first-class collection of named methods」であり、そのinclude(論文中ではuses:)は、そのtraitが(現時点で)持つメソッド集合を使うイメージのようだ。 Rubyのincludeはinheritと同様、シンボリックな関係*2を維持している。いっそmoduleもtraitのようにしてしまうとラクなんだが、多分許されないだろうなあ。

*2 includeしたmoduleにメソッドが追加されると追随すること

Matzにっき(2004-01-26) - Applying Traits to the Smalltalk Collection Classes

を見かけたとき、トレイトにメソッドを追加しても、それを use した既存クラスたちがその変更に追随できないのではさすがに困るな…(つか、動的性がいのちの Smalltalk システムで、そんな“欠陥”が許されるのか?)と引っかかったのを思い出したのですが、実際には、トレイトにメソッドが追加されると同時に、それを使うクラスたちのメソッド辞書も自動的に更新されるしくみになっているので(コストの面はともかく)実用上の問題はなさそうです。

T>>fuga   ^'fuga'

C new fuga   "=> 'fuga' "