Smalltalk-72で遊ぶOOPの原点:宇宙船を描く(失敗編)

アラン・ケイの“メッセージングによるプログラミング”という着想に基づき(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72に実際に触れてみるシリーズ 第2弾です(なお最新のSmalltalkについては Pharo などでお楽しみください!)。

今回は謎言語「Smalltalk-71」で書かれたスペースウォー・ゲームSmalltalk-72に移植して動かすことを目指します。前回(2019年)を含む他の記事はこちらから→Smalltalk-72で遊ぶOOPの原点 | Advent Calendar 2023 - Qiita


まずは Smalltalk-71 の「ship」を Smalltalk-72 にトランスパイル!

手始めに簡単そうな冒頭の to ship …Smalltalk-72 に書き直すところから始めてみましょう。

Smalltalk-71 のコードの該当部分を OCR したものがこちらです。

to ship :size
  penup, left 180, forward 2 * :size, right 90
  forward 1 * :size, right 90
  pendown, forward 4 * :size, right 30, forward 2 * :size
  right 120, forward 2 * :size
  right 30, forward 4 * :size
  right 30, forward 2 * :size
  right 120, forward 2 * :size
  left 150, forward :size * 2 * sqrt 3
  left 330, forward :size * 2
  right 60, forward :size * 2
  left 330, forward :size * 2 * sqrt 3
  penup, left 90, forward :size, right 90, forward 2 * :size
end to

[2023-12-04 追記:最後の right 60 との誤りと思われていた記述は、誌面では正しく right 90OCR の誤認識でしたので、以降もしれっと無かったことにしています。あしからず^^;]

to は Logo のプロシージャ定義キーワードの to を模したものと思われます。続く :sizeship をコールしたときの仮引数、それ以降の参照にも同じ :size を使うことも含め(カンマによる式の区切りや end to などの一部の例外を除けば)、この Smalltalk-71 のコードはほぼ Logo のそれを再現したものになっていますね!

ところで Smalltalk-72のクラスは実質、クロージャーのようなもので実現されています。インスタンスを生成しない(すなわち、isnewをメソッドに含まない)クラスを「アクション」と呼んで、よく関数代わりに使います。クラスの定義はアクション to へのメッセージ送信で行いますが、見た目は Smalltalk-71 同様、Logo の to構文と似たものになっているように見えます。

しかし、Smalltalk-72 のクラスには(関数のようにコールされはするものの)特に仮引数宣言のための構文のようなものは用意されていません。そこで、Smalltalk-71で書かれたこのshipを再現するには、①テンポラリ変数としてsizeを宣言(to ship size (…)し、②引数として送られた数をsizeの初回参照時にメッセージとしてフェッチ :size(念のため、LogoやSmalltalk-71と違い、Smalltalk-72でこの記述は「アクション : へのシンボル size の送信」という意味を持ちます!)、③以降は普通にsizeで参照…というふうに書くことになります。

次いで、Smalltalk-71で書かれた ship に登場する penuppendownleft もしくは rightforwardも、Logoのタートルグラフィックス用のコマンドと一致します。

Smalltalk-72 では、これらはタートル(turtleインスタンス。グリフはスマイリー@ 記号に代入されている)に対する penuppendnturn(時計回りの right に相当。引数の符号を反転すればleftに)、go の送信に読み替えられます。また、Smalltalk-72 の turtle ではメッセージのカスケード(たたみかけて送ること)ができるので、Smalltalk-71 でカンマで表されているコマンドの区切りを、Smalltalk-72で式の区切りとしていちいち置き換える必要はなさそうです。

Smalltalk-71 のコードで forward の引数に 1 * :size とわざわざ 1:size をかけているのは、:size が整数でなかった場合にそれを整数化するためでしょうか? しかし、後半で * 2 となっていたり、1 * を付けずに :size だけ参照する記述も見受けられるため、これらを書き損じとするか、それ以前に 整数 * などとする必要がそもそも有るのか無いのかについては、まあ、謎です^^;

Smalltalk-72 のタートルへのメッセージは(Logo のタートル制御コマンドと同様に)パラメータが小数でもよいので整数化の必要はありません。ただ、sqrt 3 のような少数が含まれる式を計算するとき size * 2 * (sqrt 3) の順のままではうまく計算できない(整数 * 小数整数 になってしまう)ことを鑑みて、sqrt 3 を先頭に置いた (sqrt 3) * 2 * size の順に統一することにします。

もっともこの順序にした場合、Smalltalk-72 の演算は右結合なので、もし size が少数でも 2 * size の段階で整数に丸められてしまいます。1 * size は「size を(他は 2倍や 2√3倍かもしれないが、ここでは)等倍で使用する」というアピール以外の意味は失われ、sizeも整数を想定する必要が生じますのであとで見直さないといけない…かも^^;

ところで、sqrtSmalltalk-72 組み込みのアクションには無いので定義しないといけませんが、見たところ sqrt 3 しか使われてないので、当面は 1.73 を返す仮実装でやり過ごすことにします。

この方針で Smalltalk-72 に変換すると、ship のコードはこのように変換できます。

Snippet3ウインドウなどにコピペする際は、下のテキストをご利用ください。(ここでは文字化けして読みづらいですが、Snippets3ウインドウなどに空行を設けてペーストすることで、文字化けもほぼ解消されます。)

to sqrt x (:x. !1.73)

to ship size (
    @ penup turn `180 go 2 * :size turn 90
        go 1 * size turn 90
        pendn go 4 * size turn 30 go 2 * size
        turn 120 go 2 * size
        turn 30 go 4 * size
        turn 30 go 2 * size
        turn 120 go 2 * size
        turn `150 go (sqrt 3) * 2 * size
        turn `330 go 2 * size
        turn 60 go 2 * size
        turn `330 go (sqrt 3) * 2 * size
        penup turn `90 go 1 * size turn 90 go 2 * size
)

disp clear
@ home penup goto 200 100 pendn. ship 10

ペースト後、プロンプトと同じAltoアイコンの直後でダブルクリックにならないようにゆっくり2回クリックして sqrt および ship をAltoエミュレーター側に転送します。あくまで、キー入力のエミュレートなので、Smalltalk-72 がプロンプトを出すまで待ってから次のコードを送るようにしてください。

disp clear でウインドウ内のテキストを消せます。それに続けて@ home penup goto 200 100 pendn. ship 10を実行して ship を描いてみましょう。

あれれぇ?(←わざとらしい) 引き延ばしたゼムクリップのような図形が描かれてしました^^;

念のため、Smalltalk-71 のコードを Logo コードと見做して Logo 処理系 で実行してみましたが、やはり同じ絵が描かれるので、変換ミスということではなさそうです。

さて困ったぞ…

宇宙船を描く(たぶんこうなんじゃないか編)へ続く)