アラン・ケイ の“オブジェクト指向 ”というアイデア をもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された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
の扱いを見てみます。
文字列クラス「string」の定義
改めて、冒頭でコールされるプリミティブCODE 3
の返値に基づく条件分岐になっている(CODE 3⇒(…)
)のが特徴的です。CODE 3
は、直後の擬似コード にも書かれている通り、クラスがアクションとして呼ばれた場合のインスタンス の生成と、インスタンス のコンテキストでコールされた場合に要素の参照や代入、要素の数に関するメッセージに対してはその結果を返し(⇑)、コンテキストを抜けます。
通常のプリミティブでは実行に失敗した場合、それをコールしたコンテキストに戻ってきて、続きに記述されたコード(フォールスルーコード)を実行するだけなのですが、このプリミティブCODE 3
は失敗した場合はfalse
を返す仕組みになっていて、その結果、条件分岐の偽時処理(=通常のフォールスルーコード)のみが実行されるというちょっと変わった振る舞いをするようです。もし要素の参照の過程で([
がマッチして、それに続く数値を取り込んだ後)にto
を見つけた場合は、そこでいったん処理を中断してtrue
を返すことで、CODE 3⇒(…)
の括弧内の非偽時処理のみが実行され、結果、問題のsubstr
がコールされその結果が返される(⇑substr …
)カラク リになっているようです。
次に、ALLDEFSからto substr …
を探してそこに記述されているコードを見てみます。
「substr」アクションの定義
substr
アクションは(下流 でさらに別のプリミティブCODE 40
を呼んだり、それに受け渡す細かなパラメーターの扱いがあって読み取りにくいものの──)、確かに、自身に送られたメッセージとして与えられた4つの’引数SELF x GLOB MESS
を取り込み(:#s. :lb. :ub. :MESS.
)の後に、CODE 3
の処理の続き、つまり、修了値を改めてub
に取り込んでから、]
とのマッチを試みることから始めているように読めます。
その後、find
とのマッチが試され、first
かlast
かあるいはオプション無しか、さらにnon
が付されるか否かを総合してop
フラグを決定してプリミティブCODE 40
(おそらくはsubstr
の処理の実体)をコールしたり、あるいは←
とのマッチがあれば、all
オプションで範囲すべてを同じ要素で置き換えるか、指定された文字列のさらに部分に置き換えるか、といった処理が書かれています。想像以上に凝ったことができるみたいですね。
「substr」で提供されている機能の例
クラス変数substr
へのsubstr
アクションの代入
ALLDEFSをsubstr
アクションの定義の後を続けて読むと、次のような記述が見つかります。
クラス変数「substr」へのアクション「substr」の代入
PUT …
は、指定したクラスのプロパティへ値を設定するアクションで、string
とvector
のクラス変数substr
に、直前で定義したsubstr
アクションの参照を(#substr
)代入している様子が伺えます。
直後のdone
は、上流に記述されたt
というアクションの定義と実行
アクション「t」(temporaryのt?)の定義と実行
により開始されたコンテキストを閉じる処理で、コメントにもあるようにsubstr
などのアクションを一時的に定義するにあたってグローバルス コープを汚さないための処置であるようです。なんだかJavaScript のバッドノウハウ みたいで面白いですね。
ともあれこのようにして、継承を使わずにsubstr
という共通の処理を、文字列と配列という二つのクラス(そしてそれらのインスタンス 群)でシェアすることが可能になっているわけです。
(継承が…ない!(「is?」の実装) に続く)