re: re: 代入とメッセージング


id:sumim:20060602:p1 で、(少なくとも“ケイのオブジェクト指向”に立脚して Smalltalk を利用する際には)「すべてはオブジェクト」というよりは「すべてはメッセージング」であることを意識すべし…という話をうけての、結城さんの、

sumimさんの「すべてがメッセージングということに注目してほしいな」というエントリ(タイトル勝手に付けました)を読んで、ふと「Smalltalkでの代入は?」と思いました。変数はオブジェクトじゃないから、代入はメッセージングじゃないですよね?

という素朴な疑問に対して、Tociyuki::Diary - re:代入とメッセージング というアイデアをいただきましたので、さらにこれをうけさせていただいて(少々、結城さんの求めておられる答の方向とは違っているような気はするのですが、この流れはこれで面白いので悪ノリして…)、id:sumim:20060513:p1 の手法を用い、実際にメッセージングで代入を行なえるように細工をしてみました。

MethodContext >> doesNotUnderstand: aMessage
   | selector arguments idx |
   selector := aMessage selector.
   arguments := aMessage arguments.
   (arguments size = 1 and: [(idx := self tempNames indexOf: selector allButLast asString) > 0])
      ifTrue: [^ self tempAt: idx put: arguments first].
   ^ super doesNotUnderstand: aMessage


Tociyuki さんの疑似コードにおける var の役割は、thisContext という擬変数のそれそのままなので、#example は、こんなふうな実際動作するコードとして記述できます。

| x |
thisContext x: 0.
[x < 3] whileTrue: [thisContext x: x + 1].
^ x

なお、メッセージングに置き換えたのは代入のみで、参照のほうは簡単のため従来通りとしました。


コンパイラに再三警告を受けますが、選択して print it (alt/cmd + p) すると、結果として 3 が得られるはずです。


ところで、文法解析の関係で「++」を使った x++ のような記述こそ(コンパイラをいじれば別ですが、普通は…)許されないものの、代わりに全角の「++」を使うことで、同様の手法にて“インクリメント”も実現可能です。

MethodContext >> doesNotUnderstand: aMessage
   | selector arguments tempNames idx |
   selector := aMessage selector asString.
   arguments := aMessage arguments.
   tempNames := self tempNames.
   (arguments size = 1 and: [(idx := tempNames indexOf: selector allButLast) > 0])
      ifTrue: [^ self tempAt: idx put: arguments first].
   ((selector endsWith: '++') and: [(idx := tempNames indexOf: (selector allButLast: 2)) > 0])
      ifTrue: [^ self tempAt: idx put: (self tempAt: idx) + 1].
   ^ super doesNotUnderstand: aMessage
| x |
thisContext x: 0.
[x < 3] whileTrue: [thisContext x++].
^ x
=> 3

これでもいけます。

| x |
thisContext x: 0.
[(thisContext x++) < 3] whileTrue.
^ x
=> 3


以上の例では、変数名はメッセージ(あるいは、メッセージセレクタの一部)になってしまっていますが、変数自身をレシーバとなすよう解釈するための特別な記法と、そうして得た変数オブジェクトへの(たとえば ++ のようなメッセージの)メッセージング…という解釈での実現方法もありえるでしょう。


文法を拡張しないお手軽な例としてここで、「変数自身をレシーバとなすよう解釈するための特別な記法」を「変数と同名のシンボルのリテラル記述」に代替えさせたならば、クラス Symbol に次の2つのメソッドを追加することで、代入やインクリメントも晴れてメッセージング(レシーバである“変数”への「<== value」や「++」の送信)として記述可能です。

Symbol >> <== value
   | senderContext tempIndex sameString |
   sameString := self asString.
   senderContext := thisContext sender.
   (tempIndex := senderContext tempNames indexOf: sameString) > 0
      ifTrue: [^ senderContext tempAt: tempIndex put: value]
Symbol >> ++
   | senderContext tempIndex sameString |
   sameString := self asString.
   senderContext := thisContext sender.
   (tempIndex := senderContext tempNames indexOf: sameString) > 0
      ifTrue: [^ senderContext tempAt: tempIndex put: (senderContext tempAt: tempIndex) + 1]
| temp |
#temp <== 0.
[#temp ++ < 3] whileTrue.
^ temp
=> 3

ここまですると、今の Smalltalk よりずっとメッセージングに固執していた Smalltalk-72 の頃への“先祖返り”の様相を呈してきますね(^_^;)。


ともあれ、この代入以外にも Smalltalk においては、メソッド定義におけるメッセージパターン(メソッド定義の一行目。メソッド名やパラメータ変数名の宣言を兼ねている)や一時変数の宣言、返り値の指定、リテラルの記述など、その記述がメッセージングのスタイルになっていない“例外”は確かに存在します。ただ、これらの多くは内部的にはメッセージングで表現し直されている(あるいは、メッセージングによりしかるべく処理されている)場合が多いので、利便性などの理由があって、あえてそうしていると考えるのがよさそうです。


他方で、if 文をはじめとする多くの制御構造のように、見た目こそメッセージングスタイルであっても、コンパイル時にインライン展開されてしまうため、実際にはメッセージ(たとえば、ifTrue: [...] ifFalse: [...] のような)が送られたり、それに対応するメソッド(同、#ifTrue:ifFalse:)が起動されることはない…といったような逆のパターンの“例外”も存在します。これらは、当該メソッドの定義に手を入れるときやメタプログラミングの際に、思わぬ落とし穴になりがちなので、知って注意しておく必要がありそうです。(たとえば、#ifTrue:ifFalse: の挙動を知りたくて、ためしに定義を書き換えたとも、その変更はシステムには無視される…とか)