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 90
で OCR の誤認識でしたので、以降もしれっと無かったことにしています。あしからず^^;]
to
は Logo のプロシージャ定義キーワードの to
を模したものと思われます。続く :size
は ship
をコールしたときの仮引数、それ以降の参照にも同じ :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
に登場する penup
、pendown
、left
もしくは right
、forward
も、Logoのタートルグラフィックス用のコマンドと一致します。
Smalltalk-72 では、これらはタートル(turtle
のインスタンス。グリフはスマイリー☺
の @
記号に代入されている)に対する penup
、pendn
、turn
(時計回りの 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
も整数を想定する必要が生じますのであとで見直さないといけない…かも^^;
ところで、sqrt
は Smalltalk-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 処理系 で実行してみましたが、やはり同じ絵が描かれるので、変換ミスということではなさそうです。
さて困ったぞ…