SmallTalk R4.1 の GOTO機能を読んでみる

エイプリルフールが終了しました。わたし自身は毎年恒例の 猫のトラックボールルームの嘘レビューでしたが、なんだかマンネリなので誰も騙されてくれません。次は本当の事を書いて読者様を疑心暗鬼の坩堝に陥れようかと思う今日この頃、皆様はいかがお過ごしでしょうか。

さて、今年のエイプリルフールネタとして、わたしの一押しはやっぱり 梅澤さんの Smalltalker 有志による SmallTalk R4.1 でしょう。


「未来を発明する最良の方法は、まず模倣から」 -- by A.K

  • Rubyばりにpが使える。デバッグに最適
  • PHPのようにGOTOをサポート
  • JavaのごとくFinalクラスをサポート
  • Eiffelのようにaliasが使える!?
  • C++にあまり似ていないfriendメソッドが使える。enemyメソッドも使える!
  • 31個までのwith:のサポート
  • 組み込みのsingletonメソッド
  • ビルトインのフィボナッチベンチ用メソッド

などといった現在の言語にふさわしい優れた特徴を備えています。

SmallTalk R4.1


弾さんがエイプリルフールで取り上げていた bogusnews さんのPHP 5.4 RC1 ネタとはひと味ちがい、我ら(?)が SmallTalk R4.1 は、本当に処理系を実装してしまいました。これはある意味エイプリルフールではないのかも。

ナベアツモードゆとりモードのような単なるネタもあるのですが、一方で「これどうなってるの」な凄いやつもちらほらです。

例えば、 PHP をマネして Smalltalk に導入しちゃった GOTO 、これは、

SmallTalkGotoExample>>exampleFibonacci
    "フィボナッチ数を求める"
    | n fibArray i |
    n := 50.
    fibArray := Array new: n.
    fibArray at: 1 put: 0.
    fibArray at: 2 put: 1.
    i := 3.
self LABEL: #LoopHead.
    fibArray at: i put: (fibArray at: i - 1) + (fibArray at: i - 2).    
    i := i + 1.
    (i <= n) ifTrue: [self GOTO: #LoopHead].

    Transcript cr; show: fibArray printString.
メソッド内GOTO(SmallTalk)

と言う風に使うのですが、一体どうやって実現しているのか非常に気になります。わからなければコードを読むのが鉄則です。なので、早速コードを読んでみました、今日はそんなこんななエントリーです。

(実際に処理系を弄って、コードを読んでお楽しみ下さい、なのに、こんなエントリーを作るのは激しく野暮だとは思います...)


* * *


まずラベルの定義するメソッド、#LABBEL: の実装ですが、

Object >> LABEL: aSymbol 
    "noop"

と、こちらは何もやってない模様。あれれ。ならば #GOTO: 側で全部やってるのかな?

Object >> GOTO: aSymbol 
    | idx ctx |
    ctx := thisContext sender.
    idx := ctx method labelPositionOf: aSymbol.
    idx > 0
        ifTrue: [ctx pc: idx]

なるほど、#GOTO: の送信元メソッドから ラベル定義メッセージのバイトコード上位置をサーチして、メソッドコンテキストのプログラムカウンタを上書きしています。GOTOっぽい動き・・ではなくガチで GOTO を実現しちゃってます。凄い。つまり、こういう(↓)ことですか。



(図のネタ元:Blue Book Chapter 27


「言うは簡単」なのですが、「言う」と「やる」では違います。肝になるのは「メソッドからシンボルを特定するコード」で、これは CompiledMethod に定義されているのですが、こちらはちょっとだけ難解です。

CompiledMethod >> labelPositionOf: aSymbol

    | labelIdx selectorIdx sendLabelCode pushSymbolCode searchBytes rs |
    labelIdx := self literals indexOf: aSymbol.
    labelIdx <= 0 ifTrue: [^ 0].

    selectorIdx := self literals indexOf: #LABEL:.
    selectorIdx <= 0 ifTrue: [^ 0].

    pushSymbolCode := 16r1F + labelIdx.
    sendLabelCode := 16rDF + selectorIdx.
    searchBytes := {
        16r70. "self"
        pushSymbolCode. "pushConstant: aSymbol"
        sendLabelCode. "send: LABEL:"
        16r87 "pop"
    }.

    rs := ReadStream on: (ByteArray withAll: self).     
    rs position: self initialPC - 1.
    rs upToAll: searchBytes.
    rs atEnd ifTrue: [^ 0]. 
    ^ rs position

Smalltalk はメソッド定義時にメソッドをバイトコードコンパイルして保持する仕組みになっています。これが CompiledMethod。フォーマットはこんな感じ

で、「頭の方にリテラルフレーム、お尻にバイトコード」で出来ています。

この CompiledMethod の リテラルフレームから ラベル名のインデックス、#LABEL: セレクタのインデックスを取得して、それらを元に

self LABEL: #LoopHead.

というSmalltalkコードに対応するバイトコード(↓)

70      # self
1F + x  # pushConstants: x (x=labelIdx)
DF + y  # send: y (y=selecterIdx)
89      # pop

を手作成。このバイト列を CompiledMethodのバイトコード群のなかからサーチするという手はず(というか荒技)です。

以上でラベル付きGOTOが完成です。やってることは素直でシンプルですし、コード量もたったこれだけなのですが、実現されちゃってることは凄い、というか変態的ですよね(^^;


* * *


言語機能を変えちゃうような変態的な内容であっても、これは別に特別なプログラミングというわけじゃなくって、ごく普通の Smalltalk を使ったユーザープログラミングでしかありません。まさに

実際にSmalltalkで開発をしていると、アプリケーションを作っているのか、言語を作っているのかだんだんと区別がつかなくなってきます。そのシステムの要件にもっとも都合の良い言語を作ってしまう、それがSmalltalkプログラミングです。

お騒がせしました(SmallTalk)

のとおりで、例えば言語そのものへのプログラミングと、その言語をつかったプログラミングの間に全く垣根がないのが Smalltalk の面白くも珍しいところ。

Smalltalk のプログラムでは、String ですとかそういう基本的で半ば言語の一部のようなクラスであってもバンバンメソッドを追加しちゃったりするのは珍しくありません。今ではすっかり麻痺してしまいましたが、わたしもはじめて Smalltalk のそこら辺に触れたときには「いいのっ!?これ」と、恐ろしく不安になったことを覚えています。

この感覚は、なんだか静的型付け言語しかしらない頃に動的型付け言語をはじめて触れたときの心許なさによく似ていていて、さらにそこからもう一歩トンデモ領域に踏み込んだ感じ。

ただ、その踏み込んだ分だけ、愉しくて、楽で、面白い世界が広がっているのだから、中毒になってしまうのですよね。・・・・たとええ仕事で全然使えなくても。しくしく(T△T


* * *


さて、SmallTalk R4.1 ですが、

他のプログラミング言語で、同様の拡張ができるかチャレンジしてみるのも、面白いと思います。(これをSmallTalk R4.1チャレンジと名付けます)

お騒がせしました(SmallTalk)

と、さりげなく他言語への挑戦状を叩きつけているのが素敵です。ちょっとムチャブリすぎるのでは、とは思うのですが、世の中には変態さんがいっぱいなので、どうなるかは予断を許さないとおもったり。はてさてふふ〜。