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

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