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?」の実装)に続く)

Smalltalk-72で学ぶOOPの原点:あらためて「Hello, World!」と文字列操作

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


文字列操作…の前にクラス定義についてからの続き)

文字列の表示

文字列クラスstringの定義を再掲します。

f:id:sumim:20191208181441p:plain
[再掲] 文字列クラス`string`の定義

このprintの定義を読むと文字列を表示する方法は(REPLで、文字列に限らず、直前に評価した結果を自動的に出力する振る舞いを除けば──)2つあることがわかります。

ひとつは、文字列に対してprintメッセージを送ることで、もうひとつはそのprintを受け取ったときの振る舞いに書かれているようにdispというオブジェクトに対して← <文字列>というメッセージを送る方法です。やはりコードを読むことで分かるように、両者は同じではなく、前者は文字列の前後を'(クオート)で括って出力します。

f:id:sumim:20191208183006p:plain
2つの方法で「Hello, World」

なお、残念ながら「!」(エクスクラメーションマーク)はそのままキーをタイプしても(リターン)が表示されるだけで入力できないので、とりあえず前者の方法では今は諦めます。後者の方法では、文字列の代わりに数値を送ることで文字を出力できるようなので、「!」のグリフが割り当てられている17を送ることで対応しています。

参考まで、0から127までのコードの文字には次のようなグリフが割り当てられているようです。

f:id:sumim:20191208190820p:plain
0から127までのコードの文字に割り当てられているグリフ

上で使ったdisp ← <文字列>disp ← <文字コード>で文字列や文字が出力できる他に、printのコードを読んでいて気がつくのは、文字列に[<開始位置> to <終了位置>]を送ることでRubyのように部分文字列を取り出せる機能があることや、さらにfind first <文字コード>を送ってコードで指定した文字の位置を知ることが出来る機能があること、そして、これらの定義がstringには見当たらないことです。

どうやらクラス変数substr がこの謎の鍵を握っているようですがこれについてはまた改めて。

文字列の結合

今のSmalltalkでは文字列や配列の結合は,(カンマ)を使いますが、Smalltalk-72ではRubyなどと同様に+を使うようです。もちろんこれは演算子ではなく、+ <結合したい文字列>というメッセージです。

f:id:sumim:20191208184916p:plain
文字列同士の結合

ただ、下に再掲したvectorの定義を見ると分かるように、配列には+による結合は定義されていないので、このメッセージが使えるのは文字列だけであることがわかります。

f:id:sumim:20191208184701p:plain
[再掲] 配列クラス`vector`の定義(黄色および薄赤部分はCODE 3の擬似コード

f:id:sumim:20191208184947p:plain
配列で同じ操作をしようとすると…

f:id:sumim:20191208185050p:plain
エラーになる

ちなみに配列の操作にはvecmodという専用のアクションが用意されています。これについてもまた別の機会に。

f:id:sumim:20191208185536p:plain
配列操作用のアクション「vecmod」

継承が…ない!(クラス変数の利用)に続く)

Smalltalk-72で学ぶOOPの原点:文字列操作…の前にクラス定義について

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


条件分岐以外の制御構造からの続き)

文字列クラス

今さらですが、新しい言語に挑戦するときに定番の「Hello, World!」がまだでした。

では早速Smalltalk-72で「Hello, World!」を…に対して皆さんが即座に無意識のうちに「文字列を出力するには文字列オブジェクトにどんなメッセージを送れば良いのだろうか…」と考えを巡らしていたなら、それはもう、メッセージングのオブジェクト指向への扉は開かれていると考えてよいのではないでしょうか。もしそうなら素晴らしいことですね!

それはさておき、どんなメッセージを送ればよいかについては例によって定義を読むのが早そうです。まずは文字列リテラルis?メッセージを送って、クラス名を尋ねてみましょう。?の入力は?そのままではなく~チルダ)を使ってください。

f:id:sumim:20191207024638p:plain
文字列リテラルにクラスを尋ねる

これはオーソドックスにstringであるとのことですので、ALLDEFS(ブートストラップコード)からto string …という記述を探して見てみましょう。

f:id:sumim:20191207024936p:plain
「string」クラスの定義

黄色くマークしたプリミティブ(始原関数)CODE 3のおおよその内容は、薄い赤でマークしたコメント部分(文字列リテラルを代用)に擬似コードで書かれています。

クラス定義の要素

Smalltalk-72のやたら絵文字やら記号の多い癖のあるコードにもだいぶ目も慣れてきたと思うので、ここでちょっと脱線してクラス定義の式の構成について触れておきましょう。直近で取り上げたifforアクションの定義と同様に、クラスの定義にはtoアクションにメッセージを送る式で記述します。

実はクラスとアクションは本質的には同じもので、両者の違いは手続きの中でisnewアクションをコールしているかどうかという点だけです。したがって、クラス定義式の最少構成は(仮にクラス名をfooとした場合)次のような式になります。

to foo (isnew)

クラスやアクションを定義する際に()内に記述された手続き(これぞ当初の「メソッド」)は、クラスやアクションそれ自身や、クラスの場合はそのインスタンスがアクティベート(メッセージのトークンからの実体化)されたときにコールされるようです。

ここまで出てきたifforなどのアクション、vectorstringなどのクラスの定義がそうなっているように、()内の処理、つまりメソッドは、メッセージのパターンマッチ式(∢<マッチさせたいトークン>)に続く条件分岐処理(⇒(非偽時処理>) <偽時処理>)の偽時処理にまた新たなパターンマッチをネストする構成になっています。つまり、今のSmalltalkを含め他の多くのOOPLがそうであるように、メンバー関数(便宜的に「メソッド」と呼ばれている──)がメッセージに対応させて個別に定義されているわけではない、ということはすでに述べたとおりです。

あいにくisnewアクションの定義はALLDEFSにもプリミティブCODE 5としか示されておらず、擬似コードもないのでその処理内容は想像するしかありませんが、おそらく、☞x ← foo. などとクラスとして(つまりは、インスタンスを生成するアクションとして)コールされた場合はisnewは新しいインスタンスを返し(したがって、続けて条件分岐処理⇒()を書くと、多くの場合、非偽時処理として括弧内に記述された初期化の手続きが実行される──)、そうでなければfalseを返す仕様になっているのだと考えられます。

変数の宣言

to <クラス名> の後は、メソッド中で使われる一時変数の宣言が続き、必要なら:(コロン)を挟んでインスタンス変数の宣言、さらに必要ならもう一つ:を挟んでクラス変数の宣言を挿入することができます。

ここでちょっと注意したいのは、「Open the ST-72 Manual」ボタンでダウンロードして読むことができる「Smalltalk-72 Reference Manual」(1976年刊)には、この一時変数、インスタンス変数、クラス変数の区切りに|バーティカルバー、パイプ文字)を使うように記述されていることです。

f:id:sumim:20191207215034p:plain
1976年刊のドキュメントには変数宣言の区切りは「:」ではなく「|」が使われている

これは、1974年に使われていたことが冒頭に記されているALLDEEFS(ブートストラップコード)、および、このエミュレーターで動いているメモリダンプベースの実装と、件の1976年刊のリファレンスマニュアルが対象にしているSmalltalk-72処理系実装との間には明らかな齟齬があることを示しています。したがって、マウスのクリックがフックできなかったり、ファイルの入出力に対応していないなどのエミュレーターそれ自体の不備や制限に加え、処理系がマニュアルのそれより古いことによりうまく動かないコードがあったり、動くとしてもある程度の読み換えが必要である可能性を念頭に置かなければならないことを意味します。

なお、クラス変数がクラスおよびそのインスタンス群から値を参照したり代入したりできる共有変数なのは今のSmalltalkRubyなどと一緒です。この次の世代のSmalltalk-76から、SIMULA 67スタイルのクラス(つまり、メンバー関数の動的コールをメッセージと称する「省コスト化」を伴って──)が採用されて以降は、同時に導入された継承機構との兼ね合いでトラブルメーカーとなるわけですが、これはそのそしりを受ける前の話ということになります。

stringクラスにはsubstrというクラス変数が定義されていますが、その使い方が継承機構に絡めて興味深いので、これについては追々ご紹介しようと思います。

あらためて「Hello, World!」と文字列操作に続く)

Smalltalk-72で学ぶOOPの原点:条件分岐以外の制御構造

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


条件分岐式についてもう少しからの続き)

Smalltalk-72のforループ

いわゆるforループは for <変数名> ← <開始値> to <終了値> by <差分> do (<処理>) のように書きますが、条件分岐が 'if' アクション(オブジェクト)へのメッセージ送信で実現されているのと同様に、もちろん for アクションへのそれで実現されています。

f:id:sumim:20191206095310p:plain
典型的なforループの記述と実行

例によって完全な仕様は ALLDEFS(ブートストラップコード)で for アクションの定義を読むのが手っ取り早いです。

f:id:sumim:20191206095529p:plain
ALLDEFSの「for」アクションの定義

  • for アクションの定義( to for …
  • token step stop var start exp という一時変数の宣言
  • まず、メッセージの最初のトークンを評価せずにそのまま var に取り込む( :☞var.
  • 続くメッセージで がマッチしたら、メッセージの続きを式として評価して start に取り込む( ∢← ⇒ (:start.)
  • そうでなければ、start の値は 1 に( ☞start ← 1
  • 続くメッセージで to がマッチしたら、メッセージの続きを式として評価して stop に取り込む( ∢to ⇒ (:stop.)
  • そうでなければ、stop の値は start と同じ値に( ☞stop ← start.
  • 続くメッセージで by がマッチしたら、メッセージの続きを式として評価して step に取り込む( ∢by ⇒ (:step.)
  • そうでなければ、step の値は 1 に( ☞step ← 1.
  • 続くメッセージでdo がマッチしたら、その参照を exp に取り込みプリミティブCODE 24をコール( ∢do ⇒ (:#exp. CODE 24)

差分はもちろん、初期値も終了値すらも省略可能である仕組みがよく分かって面白いですね。なお、プリミティブ CODE 24 は、一時変数を取り込んで実質的なforループ処理を実行します。コメントにもあるように後述のdone(中断して抜ける)やagain(中断して次の回の処理を実行)も使えます。

その他のループ

forループの他にも、単純なループのrepeat(停止するときは esc キーを押す)や

f:id:sumim:20191206102542p:plain
repeatループ(マウスポインタの位置「mx」「my」をタートル「😄」に追従させる)

今のSmalltalktimesRepeat:Rubytimesにあたるdoもあります。ただし、今のSmalltalkRubyが整数にメッセージを送る(体になっている──)のに対し、Smalltalk-72ではdo自身がループ回数を表す整数を含めたメッセージの受け手になっていて、結果、記述順が異なるので要注意です。

f:id:sumim:20191206103651p:plain
タートル「😄」に「100進んで右に90度向きを変える」を3回繰り返させる

doneagain

doneはループを中断して抜けるアクションです。with <値> で、ループを抜けたときの返値を指定することもできます。

f:id:sumim:20191206135255p:plain
「done」で値を指定してループを抜ける

againRubyでいうところの(redoではなく)next の役割を持つアクションのようです。

f:id:sumim:20191206114246p:plain
ループ中での「again」の挙動

文字列操作…の前にクラス定義についてに続く)

Smalltalk-72で学ぶOOPの原点:条件分岐式についてもう少し

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


配列要素の参照と要素の代入の実装からの続き)

Smalltalk-72の条件分岐式

第三世代目のSmalltalkであるSmalltalk-80以降(今のSmalltalkを含め)では、条件分岐もメッセージ式で表現することが比較的よく知られています。次の式は、今のSmalltalkで、「3 < 4 ならば 5 を、そうでなければ 6 を返す」という式です。

3 < 4 ifTrue: [5] ifFalse: [6]

3< 4 というメッセージを送った結果の true に対して ifTrue: [5] ifFalse: [6] というメッセージが送られる(と解釈する)ことで条件分岐を表現しています。

一方で、Smalltalk-72は制御構造としての条件分岐式 ⇒() を用意しています。通常のメッセージと同様にトークンに分解されメッセージの一部となりますが、レシーバーが解釈する前に処理系によっていわば予約語として特別扱いされ処理されます。

3 < 4 ⇒ (5) 6

f:id:sumim:20191205000036p:plain
Smalltalk-72で「3 < 4 ならば 5 を、そうでなければ 6 を返す」

ALGOLライクなif-then-elseの実装

Smalltalk-72の条件分岐式に関連して、ブートストラップコードであるALLDEFSを読んでいるとこんな定義が登場します。

f:id:sumim:20191205000402p:plain
Smalltalk-72に組み込みのALGOLスタイルの if-then-else の定義

コメントにもあるようにこれはALGOLライクな、つまり、通常の言語の条件分岐式とよく似た記法を、その意味するところは冒頭の if というアクション(オブジェクト)に対して、偽・非偽値(を返す式)、then、非偽時の処理、else、偽時の処理 といったもろもろの必要な情報をすべてメッセージとして送りつけることにより記述可能にするという実に驚きのアプローチです。

定義を細かく見てみましょう。

to は「クラス」もしくはインスタンス生成能を持たないクラスである「アクション」を定義するためのオブジェクト(これ自体もアクション)です。ここでは if というアクションを定義していて、続く exp はこのアクション内で用いられる一時変数の宣言です。その先に続く括弧内のがメソッド(つまり、if に送られてきたメッセージにどう対処するかを記述したコード)で次のような手続きが記述されています。

  • メッセージの冒頭からのトークン列を式とみなして評価し結果を exp に代入し、その値がもし非偽ならば…( :exp⇒( )★
  • 続くトークンが then にマッチしたらあらためて続くトークン列を式として評価して結果を exp に代入( ∢then⇒(:exp. )☆
  • 続くトークンが else にマッチしたら続きからトークンをひとつ、評価せずに消費( :☞. )。マッチしてもしなくても exp を返す。(つまり、else節は省略可)
  • ☆でなければ then が無い旨をエラー。( error ☞(no then)
  • ★でなければ、続くトークンが then にマッチしたら続きからトークンをひとつ、評価せずに消費( ∢then⇒(:☞. )。▲
  • 続くトークンが else にマッチしたら続くトークン列を式として評価して結果を exp に代入して返す( ∢else⇒(:exp) )。△
  • △でなければ false を返す。
  • ▲でなければ、then が無い旨をエラー( error ☞(no then) )。

ためしに if 3 < 4 then 5 else 6 を評価してみましょう。

f:id:sumim:20191205024519p:plain
ALGOLスタイルの if-then-else の評価

たしかに動きますね。ただし、この定義だと偽値時に実行されない then節、あるいは、非偽値時に実行されない else節が式の場合、括弧でくくらないとうまく処理できなさそうです。実際に試すとたしかにエラーになります。

f:id:sumim:20191205025459p:plain
非偽の場合、else節がリテラルでないとエラー

f:id:sumim:20191205025609p:plain
偽の場合、then節がリテラルでないとエラー

f:id:sumim:20191205025637p:plain
括弧でくくれば問題なし

then節、else節にリテラルをひとつだけ書くのでなければ括弧でくくる運用が必要そうです。

ちなみに、トークンを評価せずに取り込む :☞ は初出ですが、ALLDEFSの : アクションの定義( to : … )に擬似コードがありますので参照してください。

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

Smalltalk-72の条件分岐式の落とし穴

<偽値・非偽値> ⇒( <非偽時処理> ) <偽時処理> のうち、括弧でくくられる非偽時処理だけでなく、偽時処理にも複数の式を書くことが出来ます。従って、条件分岐の後にもコードを続けるときには、この条件式全体を括弧でくくらないと、期待した動作にはならないので要注意です。

3 < 4 ⇒(5 print) 6 print. 7 print. '=> 7 は出力されない(偽時処理に含まれるため)'

f:id:sumim:20191205031655p:plain
最後の「7 print.」式は、偽時の処理に含まれるため 7 は出力されない。

(3 < 4 ⇒(5 print) 6 print.) 7 print. '=> 7 も出力される'
(3 > 4 ⇒(5 print) 6 print.) 7 print. '=> 7 も出力される'

f:id:sumim:20191205032058p:plain
続く式を(偽時、非偽時にかかわらず)実行するためには、条件分岐式を括弧でくくる必要がある

条件分岐以外の制御構造に続く)

Smalltalk-72で学ぶOOPの原点:配列要素の参照と要素の代入の実装

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


配列要素の参照と要素の代入からの続き)

ALLDEFSであらかじめ用意されたクラスの定義を調べる

では配列要素の参照と要素の代入を例に、Smalltalk-72において、(今のSmalltalkを含む)その後のOOPLで一般的になった「メッセージの一部(セレクタ-)と同じ名前のメンバー関数を動的にコール(…することを「メッセージング」と称する)」とは違ったどんなアプローチで実装されているかを見てみましょう。

「ALTO Smalltalk-72」ウインドウの右側に並んでいるボタンの中にある「ALLDEFS」を押すと、Smalltalk-72が起動時に読み込むブートストラップコードを読むことができます。

f:id:sumim:20191204110129p:plain
ALLDEFS ブートストラップコード

低レベルのクラスのコードはプリミティブ(処理系組み込みの始原関数)のコールの実装だけの場合もありますが、その場合でも直後のコメントとして擬似コードが添えられているのでどんな処理をしているのか程度の雰囲気はわかります。

配列のクラスを調べて実装を探す

オブジェクトのクラスは is? というメッセージを送ることで調べることができます。「?」は「?」キーそのままではなく、 KeyboradHelp にあるように「~」(チルダ)をタイプする必要があるので注意してください。do-it (「\」キー。グリフは「!」)で評価すると vector であることがわかります。

f:id:sumim:20191204110619p:plain
配列オブジェクトが属するクラスを調べる

vector クラスは ALLDEFS では「to vector …」で始まる式で定義されています。起動フェーズごとに2回定義されていますが、2回目の定義を見てみましょう。

f:id:sumim:20191204115441p:plain
vectorクラスの定義(薄い赤でコメント部分を示した)

クラス定義式についての細かなことの説明がまだですが、変数の宣言を終えたあと「 (CODE 3 … 」とあるので、まず、プリミティブ CODE 3 を呼ぶ手続きになっていることはだいたいわかるかと思います。この CODE 3 のおおよその内容は他のプリミティブと同様に直後のコメント(「'」(シングルクオート)で括られている部分。実際は文字列リテラル。つまり文字列をコメント代わりにする運用らしい)に擬似コードで示されています(図の赤い部分)。この中の黄色で囲った部分が今知りたい配列要素の参照と要素代入の実装を端的に表した擬似コードです。

f:id:sumim:20191204120016p:plain
配列要素の参照と要素の代入の擬似コード

目玉マーク(∢)と条件分岐(⇒())、そして、評価して取り込み(:)

目が慣れないと何が書いてあるのかさっぱりですが、メッセージとして送られたトークン列を見るパターンマッチ式である「∢<マッチさせるトークン>」と、条件分岐「<偽・非偽値>⇒(<非偽時処理>)<偽時処理>」、メッセージの部分を構成する式を評価して値を取り込む「:<取り込む先の変数>」、そして今のSmalltalkと同様に値を返すリターン(ただしグリフは「↑」や「^」ではなく「⇑」)が分かれば、おおよそこの擬似コードが伝えたいことはくみ取れるかと思います。

つまり、読み下すとこんな感じになります。

  • 送られてきたメッセージの冒頭で [ がマッチしたら…( ∢[⇒(… )
  • 続くメッセージ中の式を評価して結果を一時変数 x に取り込み…( :x. )
  • 括弧を閉じるのに使われるはずのトークン ] を消費( ∢] )。
  • 残りのメッセージ冒頭で←がマッチしたら…( ∢←⇒(… )★
  • 続くメッセージ中の式を評価して結果を一時変数 y に取り込み… ( :y. )
  • y を返す( ⇑y )…のと並行して配列の x 番目の要素を y で置き換える。
  • ★でなければ、配列の x 番目の要素を返す。

[ ] や [ ]← といった名前のメソッドが定義されていてそれを呼ぶ、Smalltalk-76やRubyのアプローチとはずいぶん違った実装になっていることを(まあ、これはあくまで擬似コードなので実際はもう少し複雑なことをやっていますが雰囲気だけでも)感じ取っていただけますでしょうか?

メッセージを受け取ったオブジェクトがそれを読み解いてゆく様子が(独立した関数などでない、文字通りの──)“メソッド”として、BNF法で文法を記述するように、あるいは、Lispのリーダーマクロに近い感覚で記述されていて面白いですね。同時に、(メッセージングの──)OOPLといっても、“メソッド”と称したメンバー関数の動的呼び出しで実装するだけが唯一のやり方ではないのだよ!ということがよく分かります。

条件分岐式についてもう少しに続く)

Smalltalk-72で学ぶOOPの原点:配列要素の参照と要素の代入

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


LivelyWeb版Smalltalk-72についてからの続き)

配列オブジェクトの生成

Smalltalk-72にも今のSmalltalkや他の一般的な言語同様に動的な配列生成式が書けますが、要素がすべてリテラルならリテラル配列式で生成するのが簡単です。今のSmalltalkは #(1 2 3 4) のように括弧の前に「#」を使いますが、Smalltalk-72 では「☞」(右方向指さしマーク)を代わりに用います。入力はKeyboardHelpにもあるように「"(ダブルクオーテーション)」です。評価の際に do-it (グリフは「!」、キーは「\」)の入力をお忘れなく。

リテラル配列式

今のSmalltalkと同様に、リテラル配列式の場合、変数による参照も含めて式による要素の動的生成はサポートしていない(もっとも、最近のSmalltalkは擬変数「true」「false」「nil」については便宜上例外的にリテラル扱いにしていますが──)ので、要素を生成する式のようなつもりで計算式を混ぜても、式のトークンが要素になるだけなので注意してください。

リテラル配列式の要素はリテラルだけなので注意(式を書いても評価はされず、式の各トークンを要素とする配列になる)

もし、式を用いて要素を動的に生成したい場合は、今のSmalltalkと同じように { } でくくる、配列の動的生成式を使います。ただし、今のSmalltalkは式の区切り(つまり要素ごと)に、式の区切りである「.」(ピリオド)を、一般的な言語では「,」(カンマ)が必須なのに対し、Smalltalk-72ではこうした式の区切りは無用です。

配列の動的生成

面白いのは式(要素)の区切りが“不要”(あってもなくてもよい)なのではなく“無用”(あってはいけない)である点です。どこが式(要素)の区切りになるかは、要素を生成する各メッセージ式のレシーバー(メッセージの受け手であるオブジェクト)により動的に判断されます。うっかりいつもの習慣でピリオドやカンマなどを入れてしまうとエラーになるので注意してください。

動的配列生成式にピリオドを入れてしまったときのエラー

なお、エラーが出たときにポップアップするサブウインドウ(REPL)は done と入力して評価(「\」をタイプ)すると消すことができ、元のウインドウに復帰できます。

エラー時REPLからの復帰(done を入力して評価)

エラーからの復帰後(エラーを起こした直前の式の評価はキャンセルされる)

追記:要素を区切りを明示的にするには、{1 (2+3) 4}のように要素を生成する式ごとに()で括ってやればよいようです。

変数への代入

変数への代入は、今のSmalltalkRubyなどのシンボルに似た扱いのアトムへの「← 値」というメッセージ送信で行われます。アトムの生成も配列のリテラル式同様「☞」を使って表現します。代入記号としての「←」はSmalltalk-80やSqueakの初期バージョンと同様に「_」で入力できます。

変数への代入

なお、この図の最初の式のようにREPLで評価する式の最後にピリオドを追加しておけば、評価時に最後の式の値の出力をいちいちしなように抑制することも可能です。ちなみに、次の世代のSmalltalk-76の doIt(今のSmalltalkの print it に相当)もこれと同じ運用になっていましたが、Smalltalk-80以降の今のSmalltalkでは末尾のピリオドの有無にかかわらず最後の式の結果が戻り値(print it なら出力値)になるように変更されています。

配列要素の参照

配列の要素の参照は arr[3] のように記述します。

配列要素の参照

省コスト版のメッセージング機構を採用した次の世代のSmalltalk-76の arr ◦ 3 や、今のSmalltalkの arr at: 3 とはずいぶんと様子が違って、一般の言語に近い表現になっているのが面白いですね。

配列要素の代入

配列の要素の代入は arr[3] ← 4.56 のように記述します。

配列要素の代入とその結果の確認

こちらも次世代のSmalltalk-76の arr ◦ 3 ← 4.56 や、今のSmalltalkの arr at: 3 put: 4.56 とは違いますね。Rubyの arr[2] = 4.56 とは見た目は似ていますが、しかし、Smalltalk-76では ◦← 、今のSmalltalkでは at:put: 、そしてRubyでは []= というメソッド(ある種の関数)を定義してそれを使っているのに対し、Smalltalk-72では別のアプローチをとっています。

配列要素の参照と要素の代入の実装へ続く)