サブクラスから自身がオーバーライドしたスーパークラスのメソッドを呼びたいとき


より正確には、サブクラスのインスタンスから、自身が属するクラスがオーバーライドしたスーパークラスのメソッドを、自身が属するクラスに定義した別のメソッドからコールしたいときにはどうするか?…でしょうか。(^_^;)


2chの Ruby 初心者用スレへの投稿 より。具体的には、次の D#bar で B#foo を呼びたいときの対処法について。以前から漠然と、Smalltalk の super と違う Ruby の super の仕様(こういう仕様の言語ってほとんどないですよね。もしや Ruby のオリジナル? 追記:よく似たものに Eiffel の Precursor というのがありました)だと、いろいろと問題があるんじゃないかなぁ…と思っていたところなので、そのよい例としてメモ。

class B
  def foo; "B#foo" end
end

class D<B
  def foo; "D#foo" end
  def bar
    # どうする?
  end
end


Smalltalk では super は self と同様のしかし特殊な振る舞い(メソッド検索だけは、実行中のメソッドの属するクラスのスーパークラスから始める…)をする擬変数なので、このような場合でも普通に super foo と書くことができます。(以下、メタ情報を除いた加工コード(ファイルイン不可。ブラウザへのコピペが必要)にて)

Object subclass: #B

B >> foo
   ^ 'B >> #foo'

B subclass: #D

D >> foo
   ^ 'D >> #foo'

D >> bar
   ^ super foo
D new bar   " => 'B >> #foo' "


一方、Ruby の super は、Smalltalk の super のような擬変数(メッセージレシーバ、あるいは、ドットアクセス演算子の第一オペランドとして使用される…)ではなく、スーパークラスの同名メソッドの エイリアスなので 別名メソッドがごとく(たとえるなら、super という名前に alias した後、private 属性を付したかのように…)振る舞うので、こうしたケースには役に立ちません。そこで、二つの解決策が提示されています。(「エイリアスなので」という表現には語弊があるので緑色部分として書き換え、書き足しました。rubyco さん、ご指摘、痛み入ります。)

エイリアスを用いる alias で別名を作る方法
class D1 < B
  alias :super_foo :foo
  def foo; "D#foo" end
  def bar
    super_foo
  end
end
D1.new.bar   #=> "B#foo"
メソッドオブジェクトを引っこ抜いて直接叩く方法
class D2 < B
  def foo; "D#foo" end
  def bar
    self.class.superclass.instance_method(:foo).bind(self).call
  end
end
D2.new.bar   #=> "B#foo"


前者は D1#foo の定義前に alias しておくというのがミソですね(というか、alias の使用パターン?)。後者は PythonJavaScript チックな発想ですね。SqueakSmalltalk で無理矢理表現するなら、

D >> bar2
   ^ (self class superclass lookupSelector: #foo) valueWithReceiver: self arguments: #()
D new bar2    " => 'B >> #foo' "


という感じでしょうか。


id:sumim:20061203:p1 へ続く。



ところで、これは先に述べた Smalltalk における super の仕様から、次のように書いてもよさそうに思います。しかし残念ながら、この記述では期待どおりの動作をしません。

D >> bar3
   ^ (super class lookupSelector: #foo) valueWithReceiver: self arguments: #()
D new bar3  " => 'D >> #foo' "


理由は、Squeakバイトコードインタープリタが、メッセージ「class」を実際には送信していないからです。ちょっとした衝撃の事実ですね(^_^;)。その代わり、この「class」に限っては常にハードコードされたバイトコード(16rC7。インタプリタ内では #bytecodePrimClass)を直接コールすることになっています。同様の理由で、#class はオーバーライドすることもできないので、注意が必要でしょう。こうした例外には #class の他に、#==(バイトコード 16rC6。同、#bytecodePrimEquivalent)があります。

Re: 残念ながら、Smalltalkerのようなイメージでプログラムを見ることができない…


矢沢久雄のソフトウエア芸人の部屋 : 【第3回】 このごろオブジェクト指向セミナーの人気が高まっています より。


正直、あまり残念がる必要はないと思います。ケイのオブジェクト指向とストラウストラップのオブジェクト指向は、同じ「オブジェクト指向」を称してはいるものの、その視点や立ち位置はぜんぜん違うものですから…。むしろ合点がいったらコワイくらいです。w


現在主流の後者のオブジェクト指向の立場(カプセル化、すなわち“データ型”の設計を重視…)の考え方では「プログラムは、処理とデータからできている」で十分で、処理の動的な呼び出しをメッセージングと解釈するかどうかはその人の好み次第…というより、本来は無用のものです。むしろメッセージングなどというものは徹底的に排除して考える習慣をつけたほうが、ずっとその本質(抽象データ型とは何で、それをクラスで実装するメリットは何か…)にたどり着きやすいと思います。


ケイの「メッセージング」というアイデアは(もちろん万能ではありませんが…)たいへん強力であるため、いろんなものを“浸食”しがちです。シンパとしては嬉しい限りなのですが、それによって“汚染”させられた、他の人の優れたアイデア(ここではたとえばストラウストラップの考える「オブジェクト指向」…)が正しく伝わらなくなったり、理解しにくいものに変質させられてしまうのは、ちょっと残念に思います。