2012年7月4日水曜日

[ドリトル] メソッドの定義


Dolittleでは、生成したオブジェクトにメソッドを定義することができる。

ポイントは「クラスに定義を追加」するのではなく、生成したオブジェクトに追加するところ。

t1 = Turtle!create.

t1:square = [
  | x |
  self ! 45 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 45 leftturn.
].

t1 ! 50 forward.
[ 
t1 ! 30 square.
t1 ! 50 forward.
] ! 4 repeat.

既定のメソッドも上書きできる。
t1 = Turtle!create.
t1 ! 50 circle.
t1:circle = [
  | x |
  self ! 45 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 45 leftturn.
].
t1 ! 50 circle.

最初の「t1 ! 50 circle.」では円を描いていたのに、 最後の「t1 ! 50 circle.」では四角を描くようになってしまった。
メソッド定義は、オブジェクトを複製しても引き継がれる。
t1 = Turtle!create.
t1:circle = [
  | x |
  self ! 45 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 90 leftturn.
  self ! (x) forward.
  self ! 45 leftturn.
].
t2 = t1 ! create.
t2 ! 50 circle.

t1 から作成されたt2も circle を実行すると四角を描く。
実際には大元の「Turtle」もクラス定義みたいなものではなく初期オブジェクトに過ぎないので、Turtle!createで作ったオブジェクトは単にTurtleオブジェクトに定義されている振る舞いを引き継いでいるに過ぎない。



■メソッドの定義はプロパティの定義


上で使った「t1:square =[...]」という記法は、実は「t1オブジェクトのプロパティ square にブロックを格納した。」という意味になる。実行ブロックはプロパティや変数に格納することができる。

t1 = Turtle!create.
square = [
  | t x |
  t ! 45 leftturn (x) forward.
  t ! 90 leftturn (x) forward.
  t ! 90 leftturn (x) forward.
  t ! 90 leftturn (x) forward.
  t ! 45 leftturn.
].

t1 ! 50 forward.
[ 
  square ! (t1) 30 execute.
  t1 ! 50 forward.
] ! 4 repeat.
これは最初のプログラムと同じ動作をする。意味としては大域変数 square に 四角を描くブロックを格納してそれを実行している。

(t) ! 45 leftturn (x) forward. 
の部分はメソッドの連続実行。同一オブジェクトのメソッドの実行はこのように続けて描くことができる。まとまった処理を描くときに便利な記法。

最初のプログラムでは t1 ! 50 forward. と書いていたが、ブロックを参照しそれを実行するという意味では、 (t1:forward) ! 50 execute. として呼び出すこともできる。ただ、こうして呼び出すとt1のメソッドを実行しているわけではなくなくのでブロック内のselfが「ルート」になり、leftturnなどのメソッド群が解釈できなくなってエラーになってしまう。



■プロパティは引き継がれる


作成した子オブジェクトが親オブジェクトで定義したメソッドが使えたことからわかるように子オブジェクトは親オブジェクトのプロパティを引き継いでいる。これは作成時に引き継いだのではなく、作成後も親のプロパティを参照している。つまり親オブジェクトのプロパティが変更されると子オブジェクトの値も変わる。
t1 = Turtle!create.
Turtle:circle = [
  | x |
  self ! 45 leftturn (x) forward.
  self ! 90 leftturn (x) forward.
  self ! 90 leftturn (x) forward.
  self ! 90 leftturn (x) forward.
  self ! 45 leftturn.
  self.
].
t1 ! 50 circle.
t1 作成後に Turtle の circle プロパティが更新されているのだが、 t1!circle も四角を描くようになってしまった。
ちなみに、メソッドの最後はカスケード呼び出しができるようにselfを返しておく。




■親のメソッドの呼び出し方

どうするのだろうと思っていたら小技があった。 ブロック内で当該メソッド名と同じローカル変数を定義しておけばよい。
詳細は こちら

forward を上書きして、道程の真ん中で円を描くようにしてみよう。

t1 = Turtle ! create.
t1:forward = [ | step ; forward |
  ! (step/2) forward.
  ! 20 circle.
  ! (step - (step/2)) forward.
  self.
].

t1 ! 200 forward 90 rightturn 200 forward 90 rightturn.
t1 ! 200 forward 90 rightturn 200 forward 90 rightturn.
forward メソッドの定義の中で、親オブジェクトの forward を使うためにローカル変数 forward を定義している。そのために、「! (step/2) forward.」実行時に、自オブジェクトのforward が見つからずに親オブジェクトの forward を引き当てるようだ。
「ローカルブロック内」 → 「自オブジェクト内」 → 「親オブジェクト」
と探索するのではなく、自オブジェクト内探索を飛ばして親オブジェクトに行くらしい。

ちょっと理解が難しいみたい。

なお、

t1 = Turtle ! create.
t1:forward = [ | step ; forward |
  ! (step/2) forward.
  ! 20 circle.
  ! (step - (step/2)) forward.
  self.
].

t1 ! 100 6 polygon.

とやっても普通の6角形を描く。つまり polygon 命令の中から forward が呼び出されているわけではないようだ。

0 件のコメント:

コメントを投稿