局所変数無き荒野を生きる
古の超最先端言語である Self の際立つ特徴は二つにまとめられます。
- プロトタイプベース
- 局所変数がない
Self はしばしばプロトタイプベースのオブジェクト指向計算機言語の代表格に挙げられますが、そのもう一つの重要な側面である局所変数がないという特徴にはあまり注目されません。実際のところ大域変数すらあるわけではなく、Self においては「変数」という言葉は全てオブジェクトの「スロット」によって置き換えられていると考えて良いのです。おお、なんてこった!最先端過ぎてついて行けないよ!
"こういうのは全部実はメッセージでスロットの値を得ているだけ" traits oddball "オブジェクト shell のスロット traits の値であるオブジェクトの" "スロット oddball の値を取得" "大域変数がないからスロットを使ってるってワケ!" (| x: = (|arg:| arg + y). y = 1 |) "メソッド x: における arg だって" "暗黙のメソッドオブジェクト self にメッセージ arg を送ってます" "だから局所変数じゃないよ!" [|:x| x] "このブロックにおける x は" "暗黙のブロックオブジェクト self にメッセージ x を送ってます" "だから局所変数じゃないよ!"
メソッド内における暗黙の self がメソッドオブジェクトだったら、コード arg + y における y のレシーバも同様にメソッドオブジェクトになるから困らない?と不思議に思うかも知れませんが、メソッドオブジェクトは parent slot として self* というのを持っていて、その値がメソッド所有者のオブジェクトになっているから delegation でうまいこと y の値を取得できます(詳細は Self Reference Manual の 2.2.3 Slots containing methods をご覧下さい)。とっても賢いですね、これぞまさにプロトタイプベースがこそ!
メソッドの中では暗黙の self がメソッドオブジェクトになっていることはとても分かりづらいですよね。
inspect: (| x = (self) |) x "=> ( | x = ( self). | )"
以上のように、(| x = (self) |) というオブジェクトは self という名前のスロットを持っていませんが、メソッドの中ではメソッドオブジェクトに対するメッセージ self によって ( | x = ( self). | ) が返されます。これは暗黙の self が実はメソッドオブジェクトで、またスロット parent を持っているからです。
とは言え、実装的には上で書いたようにはなっていないような感じなんですけどね(じゃあ今まで書いたのはなんだったの!と思ったらごめんなさい)。この証拠として "self" は予約語扱いになっていてスロット名としては使えないようです(つまり (| self* = 1 |) のような表現は統語違反扱いになります)。しかし Reference Manual を読む限りではメソッドオブジェクトはこれまで書いたようになっていますし、そうしたように実装することは可能なはずです。しかしそうなっていないのは効率のためなのかも知れません。他にもメソッドスロットが再定義不可能だったり、メソッドオブジェクトまわりは理論と実装に多少の乖離があるような気がします。
さてところで、もちろんプロトタイプベース言語だから変数が無い必要があるというわけではありません。プロトタイプベース言語だけど Self っぽさのない Javascript には変数があります。あまり詳しくないけれども NewtonScript にも局所変数があるようです(少なくとも用語としては変数って言ってるのでそう判断しました。実際どうなのかは自信がないので各自確かめてみて下さい。もし間違っていたら教えて下さい)。
じゃあ Self の精神を色濃く受け継ぐ iolanguage はどうなの?
Io> x := 1 ==> 1
これは一見すると局所変数のように見えますが、ちゃんとスロットに対する値の割り当てになっています。
Io has three assignment operators:
operator action
- = Creates slot, creates setter, assigns value
:= Creates slot, assigns value
= Assigns value to slot if it exists, otherwise raises exceptionThese operators are compiled to normal messages whose methods can be overridden.
http://www.iolanguage.com/scm/git/checkout/Io/docs/IoGuide.html
実際やってみます。
Io> slotSummary ==> Object_0x805e5c0: Lobby = Object_0x805e5c0 Protos = Object_0x805e058 exit = method(...) forward = method(...) Io> x := 5 ==> 5 Io> slotSummary ==> Object_0x805e5c0: Lobby = Object_0x805e5c0 Protos = Object_0x805e058 exit = method(...) forward = method(...) x = 5
Lobby オブジェクトにスロット x が作成されました(ちなみに iolanguage のスロットは全て read/write slot のようです)。以上のように iolanguage は Self 同様に局所変数を持ちません。でもこれらの演算子によってまるで局所変数があるのと変わらないくらい便利に使えます(Self ではこれらの代わりに _AddSlots: メッセージをいちいち送る必要があります)。さすが!
でも、Self にせよ iolanguage にせよ、スロットに局所変数相当のなんか変なのが追加されるの、嫌じゃないですか?基本的には局所変数のようなものが必要のないようにプログラムを組むべきではあるはずなのですが(実際のところ、Self world はなるべくそうした必要のないように設計されているようですから、局所変数がなくても困らないはずです)、どうしても欲しい場合もあるでしょう。Self ではそんな時にはブロックオブジェクトを使えば良いと思います(iolanguage だとメソッドオブジェクトでも大丈夫ですよね)。
例えば以下のようなつまならない例(rubyで書きました)を考えてみます。
x = rand(20) p (if x < 10 then x else 10 end)
こうした状況は Self では次のように書けば良いものと思われます。
[|:x| x < 10 ifTrue: x False: 10] value: random integer: 20
これを ruby で書き直すと次のような感じになります。
proc {|x| if x < 10 then x else 10 end}.call(rand(20))
このようにブロックオブジェクトを利用して頑張れば(実際的なプログラムを組む場合にはひどくネストしたブロックを作っちゃいそうです)、私のような凡人でも局所変数のない美しいプロトタイプの荒野を生き残れるのではないかと考えています。
で、実際これでいいの?という疑問がどうしても脳裏を駆け巡るわけですが、本当はどうするべきなんでしょうか。誰か教えて下さい。iolanguage なら実用している人が居ると思うのですが、局所変数がなくて困る事、ありませんか?