Smalltalk-72で学ぶOOPの原点:知らないメッセージはスルーする?

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


継承が…ない!(「is?」の実装))からの続き)

知らないメッセージは基本スルーするSmalltalk-72

isパターン未定義時のis?の挙動の不思議はひとまず置いておくとして、Smalltalk-72でオブジェクトが知らない(パターン未定義の)メッセージを受け取ったときの挙動を確かめておきましょう。

ためしに3hogeを送ってみます。

f:id:sumim:20191211175711p:plain
「3」に「hoge」を送ってみる

すると、エラーになります。

f:id:sumim:20191211175827p:plain
エラーが出るが…

ただ「シンボル(hogeを指す)は値を持たない」というちょっと不思議なエラーになります。

ちなみに参考まで、今のSmalltalkでは「そんなメッセージ、知らない」がこういうときのエラーの基本です。

f:id:sumim:20191211180341p:plain
Pharo Smalltalkで「3」に「hoge」を送ったときのエラー

実は、Smalltalk-72では知らないメッセージは基本スルーする仕組みになっており、したがって、続くメッセージを新しい式の始まりと認識されます。つまり、続くメッセージの最初のトークンは次のメッセージ式のレシーバーになるので、hogeは変数として認識され、それに値が代入されていないという件のエラーになるわけです。

試しに、あらかじめhoge4を代入しておいてから改めて3 hoge を評価してみましょう。

f:id:sumim:20191211180735p:plain
あらかじめ「hoge」に「4」を代入して「3 hoge」を実行すると…

なるほど4が返ります(このとき3はREPLで複数の式を評価したときの一つ前の式と同じ扱いとなり、普通に破棄され結果=出力には影響を与えません)。

知らないメッセージは基本スルーというのは、悪名高きObjective-Cnilの挙動(もっとも彼はオブジェクトですらないので、知っているメッセージなど無く、すべてのメッセージをスルーするわけですが…)を彷彿とさせて、なんだかちょっと残念ですね。これについては、アラン・ケイのアイデアに忠実にした結果なのか、はたまたただ単純に実装の都合や運用の妙の類いに過ぎないのか、もう少し調べてみたいと思います。

isアクション

ということで、実はALLDEFS(ブートストラップコード)にはto is …で始まるisアクションが別に定義されていて、isメッセージに対応できないオブジェクトにis?等のメッセージが送信されたとしても、レシーバーがそれに応答する代わりにisアクションがコールされ(それに改めて?が送られることで)untypedを返すことができる、というのがこのカラクリの答でした。

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

念願のクリック、's(instance_evalで使う記号)、⦂(オープンコロン)を手に入れた!に続く)

Smalltalk-72で学ぶOOPの原点:継承が…ない!(「is?」の実装)

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


継承が…ない!(クラス変数の活用)からの続き)

クラスを問い合わせるis?

継承がないということは、Objectに代表される“すべてのオブジェクトの振る舞いを決める基底クラス”もないわけで、つまり、クラスを問い合わせるメッセージis?のような基本操作にも、そのパターンマッチをいちいち定義しておかなければ応答がままならないことを意味します。

f:id:sumim:20191210104251p:plain
タートル「😄」、文字列「'abc'」、配列「☞(1 2 3)」は「is?」に応答できるが…

f:id:sumim:20191210120113p:plain
それは、これらのクラスで「is」のパターンマッチが明示されているから

多くのクラス(のインスタンス)がis?に応答できるのは、単純にisのパターンマッチが定義されているからなのですね。

ISITの正体

ではここでeval(評価)されているISITとは何者でしょうか? 定義はこんな感じです。

f:id:sumim:20191210121413p:plain
「ISIT」の定義

続くトークンが?(タイプして入力するときは~)なら、クラス名(TITLEプロパティ)を返し、そうでなければTITLEプロパティと続くトークン(評価せずに取り込む:☞)と等価ならそのまま、等価でなければfalseを返す…というコード片と同義の配列が、グローバル変数ISITに代入されたものとなっています。

運用時はこれにevalを送ることで評価できます。シェアしたいコード片(処理)を配列のままグローバル変数に保持し、各クラスでの記述を最小限にするという継承の無い辛みを軽減するもうひとつの工夫ですね。

f:id:sumim:20191210124028p:plain
「is」と「is?」に対する振る舞い

エラーにならないis?

クラスはisnewアクションを呼ぶアクションに過ぎないことは既に述べましたが、この最少構成であるクラス(仮にfooisのパターンマッチはあえて定義しない)のインスタンスを使ってis?にどんな応答をするか調べてみましょう。

f:id:sumim:20191210122501p:plain
パターンマッチが定義されていないのにエラーにならない「is?」

あれ? おかしいですね。fooとこそ返しませんが、untypedなどとそれっぽい答が返ってきます。これはいったいなぜでしょうか。

知らないメッセージはスルーする?に続く)

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
続く式を(偽時、非偽時にかかわらず)実行するためには、条件分岐式を括弧でくくる必要がある

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