Smalltalk-72で学ぶOOPの原点:継承が…ない!(クラス変数の活用)

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズです(なお最新のSmalltalkについては Pharo などでお楽しみください!)。他の記事はこちらから→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita


あらためて「Hello, World!」と文字列操作からの続き)

クラス変数substrは何に、そして、何のために使われているのか?

Smalltalk-72には継承機構はありません。これはすなわち、アラン・ケイの「メッセージングのオブジェクト指向」においては、継承により明示的に部分型を意識することはおろか、そもそも“継承”すら必須ではないということを意味します。はい。ここテストに出ます!

とはいえ、BitBLT(ダン・インガルスらが発明したコンパクトで高性能な描画ルーチン)などによる高速化をきっかけにしてSmalltalk-74でGUI付きのOSモドキのそれなりの規模のシステムまでを構築するに至った経験を通じ(いや、おそらくはそれよりもずっと早い時期にすでに──)関連するクラスの間で実装をシェアする目的で継承を欠いたままではつらいということは分かっていたはずで、加えて次の世代のSmalltalk-76には継承があっさり導入された経緯を鑑みても、Smalltalk-72であえて継承機構を省いたことの是非については議論の余地はいくらかありそうです。

さて、文字列や配列クラスのクラス変数substrには、そんな継承の無い辛みを軽減するための工夫のひとつとして、文字列や配列の部分を扱うアクション(定義時には便宜的にsubstrという同名の仮の名前を使用)が代入され利用されています。ALLDEFS(ブートストラップコード)でそれらの細工が施されてゆく過程を確認してみましょう。

substrアクションの定義

繰り返しになりますがまずstringクラスの定義でsubstrの扱いを見てみます。

f:id:sumim:20191209110348p:plain
文字列クラス「string」の定義

改めて、冒頭でコールされるプリミティブCODE 3の返値に基づく条件分岐になっている(CODE 3⇒(…))のが特徴的です。CODE 3は、直後の擬似コードにも書かれている通り、クラスがアクションとして呼ばれた場合のインスタンスの生成と、インスタンスのコンテキストでコールされた場合に要素の参照や代入、要素の数に関するメッセージに対してはその結果を返し(⇑)、コンテキストを抜けます。

通常のプリミティブでは実行に失敗した場合、それをコールしたコンテキストに戻ってきて、続きに記述されたコード(フォールスルーコード)を実行するだけなのですが、このプリミティブCODE 3は失敗した場合はfalseを返す仕組みになっていて、その結果、条件分岐の偽時処理(=通常のフォールスルーコード)のみが実行されるというちょっと変わった振る舞いをするようです。もし要素の参照の過程で([がマッチして、それに続く数値を取り込んだ後)にto を見つけた場合は、そこでいったん処理を中断してtrue を返すことで、CODE 3⇒(…) の括弧内の非偽時処理のみが実行され、結果、問題のsubstrがコールされその結果が返される(⇑substr … )カラクリになっているようです。

次に、ALLDEFSからto substr …を探してそこに記述されているコードを見てみます。

f:id:sumim:20191209142710p:plain
「substr」アクションの定義

substrアクションは(下流でさらに別のプリミティブCODE 40を呼んだり、それに受け渡す細かなパラメーターの扱いがあって読み取りにくいものの──)、確かに、自身に送られたメッセージとして与えられた4つの’引数SELF x GLOB MESSを取り込み(:#s. :lb. :ub. :MESS.)の後に、CODE 3の処理の続き、つまり、修了値を改めてubに取り込んでから、]とのマッチを試みることから始めているように読めます。

その後、findとのマッチが試され、firstlastかあるいはオプション無しか、さらにnonが付されるか否かを総合してopフラグを決定してプリミティブCODE 40(おそらくはsubstrの処理の実体)をコールしたり、あるいはとのマッチがあれば、allオプションで範囲すべてを同じ要素で置き換えるか、指定された文字列のさらに部分に置き換えるか、といった処理が書かれています。想像以上に凝ったことができるみたいですね。

f:id:sumim:20191209150023p:plain
「substr」で提供されている機能の例

クラス変数substrへのsubstrアクションの代入

ALLDEFSをsubstrアクションの定義の後を続けて読むと、次のような記述が見つかります。

f:id:sumim:20191209150445p:plain
クラス変数「substr」へのアクション「substr」の代入

PUT … は、指定したクラスのプロパティへ値を設定するアクションで、stringvectorのクラス変数substrに、直前で定義したsubstrアクションの参照を(#substr)代入している様子が伺えます。

直後のdoneは、上流に記述されたtというアクションの定義と実行

f:id:sumim:20191209150947p:plain
アクション「t」(temporaryのt?)の定義と実行

により開始されたコンテキストを閉じる処理で、コメントにもあるようにsubstrなどのアクションを一時的に定義するにあたってグローバルスコープを汚さないための処置であるようです。なんだかJavaScriptバッドノウハウみたいで面白いですね。

ともあれこのようにして、継承を使わずにsubstrという共通の処理を、文字列と配列という二つのクラス(そしてそれらのインスタンス群)でシェアすることが可能になっているわけです。

継承が…ない!(「is?」の実装)に続く)