Ruby版 Classbox に Local Rebinding機構が欠けていることによって生じる、本家との挙動の違い


id:sumim:20100829:p1 の続き。

classbox.diff を当てた trunk のビルドができたので、本家の Classbox のテストコードの振る舞いを、Ruby版に書き直した結果と比べてみました。両者の挙動の違いには、おそらく、発表に対する質問にも出ていた local rebindings という機構の有無が効いてきているのだと思います。


Ruby版 Classbox
module CB1
  class A
    def foo; 10 end
    def bar; foo end
  end
 
  class B
    def foo; A.new.foo end
  end
 
  class C
    def foo; B.new.foo end
  end
 
  class D
    def foo; C.new.foo end
  end
end
 
module CB2
  def foo; 20 end
end

module CB1
  p A.new.foo   #=> 10
  p A.new.bar   #=> 10
  p D.new.foo   #=> 10

  overlay_module A, CB2
  p A.new.foo   #=> 20
  p A.new.bar   #=> 10 ; A#bar が呼ぶ #foo は元のまま
  p D.new.foo   #=> 10 ; B#foo が呼ぶ A#foo も元のまま
end
Squeak Smalltalk版 Classbox を Ruby風に書き直したものと、本来期待される挙動
classbox CB1
  import Classbox.system, [:Object]   #A、B、C、D 定義でスーパークラスとして必要

  class A
    def foo; 10 end
    def bar; foo end
  end

  class B
    def foo; A.new.foo end
  end
 
  class C
    def foo; B.new.foo end
  end
 
  class D
    def foo; C.new.foo end
  end
end
 
classbox CB2
  import CB1, [:A, :D]  #:A は、A#foo のすげ替えに必要。:D は後で D.new するのに必要。

  class A
    def foo; 20 end
  end
end

classbox CB1
  p A.new.foo   #=> 10
  p A.new.bar   #=> 10
  p D.new.foo   #=> 10
end

classbox CB2
  p A.new.foo   #=> 20
  p A.new.bar   #=> 20 ; CB2内では A#foo は置き換えられたものがコールされる
  p D.new.foo   #=> 20 ; 同上
end

今回は import をメソッド風に、同じく Ruby 2.0 に追加予定の Traits機構を担う mixメソッドを真似たものに変えてみました。^^;


▶(例によって構文がないので、手続き的ですが―)実際の Squeak Smalltalk版 Classbox のコード例とその挙動
| cb1 cb2 |
Classbox cleanTheSystem.
cb1 := Classbox create: #CB1.
cb1 import: #Object from: Classbox system.
cb1 createClassNamed: #A.
cb1 addMethod: 'foo ^10' for: #A.
cb1 addMethod: 'bar ^self foo' for: #A.

cb1 createClassNamed: #B.
cb1 addMethod: 'foo ^A new foo' for: #B.
 
cb1 createClassNamed: #C.
cb1 addMethod: 'foo ^B new foo' for: #C.
 
cb1 createClassNamed: #D.
cb1 addMethod: 'foo ^C new foo' for: #D.
 
cb2 := Classbox create: #CB2.
cb2 import: #A from: cb1.
cb2 import: #D from: cb1.
cb2 addMethod: 'foo ^20' for: #A.

World findATranscript: nil.
Transcript cr; show: (cb1 evaluate: 'A new foo').   '=> 10 '
Transcript cr; show: (cb1 evaluate: 'A new bar').   '=> 10 '
Transcript cr; show: (cb1 evaluate: 'D new foo').   '=> 10 '
Transcript cr; show: (cb2 evaluate: 'A new foo').   '=> 20 '
Transcript cr; show: (cb2 evaluate: 'A new bar').   '=> 20 '
Transcript cr; show: (cb2 evaluate: 'D new foo').   '=> 20 '