Smalltalk-72で遊ぶOOPの原点:疑似乱数発生器を実装する
アラン・ケイの“メッセージングによるプログラミング”という着想に基づき(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72に実際に触れてみるシリーズ 第2弾です(なお最新のSmalltalkについては Pharo などでお楽しみください!)。
今回は謎言語「Smalltalk-71」で書かれたスペースウォー・ゲームを Smalltalk-72に移植して動かすことを目指します。前回(2019年)を含む他の記事はこちらから→Smalltalk-72で遊ぶOOPの原点 | Advent Calendar 2023 - Qiita
16ビット用Xorshfit疑似乱数発生器
さて、そろそろ Smalltalk-72 で spaceship
を実装してキー操作や(疑似)並列化などいろいろ試したいところですが、そのインスタンス化のときの location
や direction
の初期化に際して元の Smalltalk-71版には無い適当な初期値を与える必要があります。ランダムにしておくのが順当そうですが、Smalltalk-72 にはもちろん(?) 乱数発生器は無いのでそのためには自分で用意しなければなりません。
Smalltalk-72 Instruction Manual に簡単な実装例があるのですが、少しひねりを入れてこれを Xorshift版に書き換えましょう。
Xorshiftは、こちらの16ビット用のコードを参考にさせていただきました。
以下で Smalltalk-72 May30版で書いたコードを順に見てみましょう。(コピペ用のテキストは文字化けぎみなので、図の方をご参照ください。)
to rand low high : : n ( (%seed ? (:n)) (null n ? ("n _ 12345)) "n _ n &- n &/ 7. "n _ n &- n &/ `9. "n _ n &- n &/ 8. %between ? (:low. :high. !low + n mod high + 1 - low) !(32768.0 + n) / 65535.0) rand rand between 1 10
rand
はインスタンス生成能を持たないクラス(=アクション)で、テンポラリ変数 low
および high
、クラス変数 n
が宣言されています(to rand low high : : n (...
)。
なお、上で引用した Smalltalk-72 Instruction Manual のバージョンでは、変数種類の区切りは |
ですが、エミュレーターの May30版の時点ではまだ :
が使われています。( to クラス名 テンポラリ変数宣言 : インスタンス変数宣言 : クラス変数宣言 (メソッド記述)
)
この rand
アクションは、seed
というメッセージを受け取ると( ᗉseed ⇒ (...
)、続くメッセージを式として評価して値を取り込み、クラス変数 n
に代入します(:n
)。ちなみに :n.
は、☞n ← :.
(つまり、アクション☞
への n
の送信の返値への ← :
の送信)を、:
アクション :
へのメッセージ n
の送信として表現した、同じ処理(取り込んだ値の変数への代入)の別の書き方です。
ここまできてまだ n
が nil
なら( null n ⇒ (...
)12345
を代入します(`☞n ← 12345)。
Smalltalk-72 で ビットXOR は ○
+ +
、ビットシフトは ○
+ /
の記号で表され、それぞれ &
+
もしくは &
/
の順にタイプすることで入力できます。これは ○
というグリフの文字幅が 1 と実際の文字の幅よりずっと狭く設定されており、続けて何か文字(この場合 +
もしくは /
)が表示されるとそれが重なって見えるカラクリで実現されています。
クラス number
の主要部分はプリミティブ( CODE 4
)なので show number
では肝心な部分の定義を見ることはできませんが、イメージとしては ALLDEFS のコメントに書かれているこんな感じ(次図のハイライト部分)で実装されていると思えばよいでしょう。
余談ですが、Smalltalk-80以降と違い、Smalltalk-76 までは連続した記号をまとめてひとつのメッセージシンボル(二項セレクター)としては扱えませんでした。Smalltalk-76 には特にワークアアラウンドは用意されませんでしたが、Smalltalk-72 では連なる記号を1文字ずつ確認(ᗉ
)しつつ条件分岐(⇒(...
)する処理を重ねれば、これらビット演算のように、なんとか任意の記号の組み合わせによる二項演算表現も可能だったようです。
閑話休題。
さらに rand
アクションは between
というメッセージを受け取ると( ᗉbetween ⇒ (...
)、続く2つの数値(それぞれが式で与えられている場合は、それを評価してから)取り込み、それぞれ low
および high
に代入します( :low. :high.
)。そして、まず n
について high
と low
の差に 1
を足した数の剰余を求めてから( n mod high + 1 - low
)low
にそれを加えることで、low
から high
までの値に正規化して返します( ⇑low + ...
)。
Smalltalk-72 での演算は、オペレータに相当するメッセージシンボルをルック(ᗉ
)したあと第二オペランドを、続くメッセージを式として評価してフェッチ(:
)することで得る都合上、そのほとんどが右結合になることに注意してください。これは、効率化のため動的な関数呼び出しにメッセージングの“フリ”をさせている Smalltalk-76 以降(演算は左結合)と Smalltalk-72 との大きな違いです。
最後に、send
や between
などのメッセージが送られることなく、たんに rand
とした場合は 0 以上 1 未満の値を返します( ⇑(32768.0 + n) / 65535.0
)。