元の話の発端が何かは分からなかったのですが、最近 Ruby の true, false の属するクラスについての言及
TrueClass / FalseClass が分かれてるのはSmalltalkもそうだからまあある程度理由はあったと思うんだけど。でもSmalltalkはちゃんと共通祖先のBooleanを作ってあるのにRubyにない理由はよくわからん。
— Urabe, Shyouhei (@shyouhei) 2016年6月23日
や、関連する過去のこんな記事
を見かけたので、今の Smalltalk と、その元になっている Smalltalk-80 より前に作られた Smalltalk-76、Smalltalk-72 ではどんなふうになっていたか調べてまとめてみました。
▼ 現在の Smalltalk における true, false (Smalltalk-80 以降)
先の言及にもあるように Smalltalk-80 を元にしている今の Smalltalk(Pharo、Squeak、VisualWorks などの直系の子孫。GNU Smalltalk などのファンお手製の変わり種実装を含む)では、true は Trueクラス、false は Falseクラスの唯一のインスタンスで、さらに Trueクラス、Falseクラスは共通の Booleanクラスのサブクラスになっています。
true class. "=> True " false class. "=> False " True superclass. "=> Boolean " False superclass. "=> Boolean " nil class. "=> UndefinedObject " UndefinedObject superclass. "=> Object "
Ruby と異なり、現在の Smalltalk では、if 式は true や false へのメッセージ送信として記述します。たとえば、次の式の場合、3 < 4 (これも 3 への < 4 というメッセージ送信)の結果の true に対して、ifTrue: [5] ifFalse: [6] というメッセージが送信されます。
3 < 4 ifTrue: [5] ifFalse: [6] "=> 5 "
通常のメッセージ式と同じように解釈されるならば、これは true の属する Trueクラスに定義された ifTrue:ifFalse: というメソッドを [5]、[6] という引数を伴ったコールとして機能します(実際にはコンパイル時にインライン展開されていわゆる GOTO を使ったコードに置き換えられるので、通常のコードでは ifTrue:ifFalse: などのメソッド本体がコールされることはありません。念のため)。
分かりやすく Ruby 風に(メソッド名に使えないコロンをアンダーバーに置き換えて)書き下すとこんなかんじになりますか。
(3 < 4).ifTrue_ifFalse_(->{5}, ->{6})
普段使いの Squeak4.3J で調べると、True(もしくは False)にはこんなメソッド群が定義されています。
True selectors. "=> #(#and: #| #ifTrue:ifFalse: #not #ifFalse:ifTrue: #==> #ifTrue: #or: #ifFalse: #printOn: #xor: #& #asBit) "
そのスーパークラスである Boolean に定義されているメソッド群はこんなふうになります。
Boolean selectors. "=> #(#ifTrue:ifFalse: #or:or: #and:and:and: #not #or:or:or: #and:and: #or:or:or:or:or: #& #veryDeepCopyWith: #and:and:and:and: #eqv: #| #or:or:or:or: #storeOn: #deepCopy #basicType #clone #isLiteral #ifFalse:ifTrue: #shallowCopy #==> #ifTrue: #or: #ifFalse: #newTileMorphRepresentative #and:) "
このうち、サブクラスの True, False が再定義している #(#ifTrue:ifFalse: #not #& #| #ifFalse:ifTrue: #==> #ifTrue: #or: #ifFalse: #and:) の中身はすべて self subclassResponsibility と記述されています。これらのメソッドはコールされることはないですし、コールされても例外があがるだけで意味をなさず定義は無用なのですが、きっと抽象データ型OOPの部分的サポートを意識した設計になっているのでしょうね。
▼Smalltalk-76 における true, false
Smalltalk は仕様のみで実装まで至らなかった Smalltalk-71 を除いて、大きく分けて 1970年代に Smalltalk-72、Smalltalk-76 という「二つの言語」が作られました(実際にはさらに -72 の高速版の -74、-76 のシュリンク版の -78 も作られています)。この「二つの言語」という言い回しは、通常の言語からすると妙に聞こえると思います。
プログラミング言語は、バージョンが上がるごとに機能が増えたり文法が拡張・変更されたりしつつも、言語としては同じ物と認識されるのが普通です。しかし Smalltalk の場合は違っていて、メッセージングによるプログラミングというコンセプトを共有することを除けば、Smalltalk-72 と -76 は、-80 以降の Smalltalk とは言語としてはまったく別物なので注意を要します。
Smalltalk-80 と比較的時代の近い Smalltalk-76 は、後に Smalltalk で象徴的となるメッセージ式文法やカラムUI を採用したクラスブラウザ等の IDE関連 GUIツールの存在など Smalltalk-80 と似た部分も多いのですが、一方で Smalltalk-80 でよく知られているメタクラスや、後にクロージャーで実装される第一級の無名関数オブジェクトが無かったり、いわゆる 〜ect 系のコレクションメソッド群を持たないなど、今の Smalltalk の特徴とされる機能を多く欠いていて興味深いです。
Smalltalk-76 には ⇒ を使用する後述の Smalltalk-72 スタイルの if 式の他に、通常の言語にある if式構文が用意されています。つまり Smalltalk-76 では -80 に象徴される true, false へのメッセージ送信を行なわずに条件分岐を記述可能なのです。これは Smalltalk に慣れ親しんだ者としてはちょっとした衝撃の事実ですね。たとえば先に書いた現在の Smalltalk の 3 < 4 ifTrue: [5] ifFalse: [6] という処理は、Smalltalk-76 では次のように記述することができます。
同様に、for, while, until といったループの式構文も用意されています。
こんな Smalltalk-76 ですが、本題の true, false はどのような扱いになっているのでしょうか。それぞれが属するクラスを調べてみます。処理系は 2014年にリバイブされた Smalltalk-78 を使用しました(先のスクリーンショットも同様)。
true class "⇒ Class Object " true hash "⇒ 2 " false class "⇒ Class Object " false hash "⇒ 1 " nil class "⇒ Class Object " nil hash "⇒ 0 " Object new class "⇒ Class Object " Object new hash "⇒ 4867 "
Class Object というのは単に Objectクラスのことのようです。実に面白い。なんと Smalltalk-76 では true, false そして nil は専用のクラスが用意されておらず、Object の普通のインスタンスなのです。hash が 2, 1, 0 なのも象徴的です。
▼Smalltalk-72 における true, false
Smalltalk-72 は先にも述べたとおり現在の Smalltalk はおろか、Smalltalk-76 ともまったくの別言語です。クラスは JavaScript のようにコンストラクタを兼ねた関数で、その中にメソッドがパターンマッチを用いた文法解析処理のように記述されます。クラスの継承機構はありません。したがって self subclassResponsibility に象徴されるような、現在主流の抽象データ型のOOP の汚染を受けていないので、アラン・ケイのメッセージングOOP の心を学ぶには、ぜったい外せない処理系とも言えます。
参考まで、簡単な Joe the box デモ(下のツイートを参照)程度であれば Smalltalk-78 同様、Lively-Web にリバイブされた ALTO/Smalltalk-72 エミュレーターが手軽に使えます。
.@esehara 件のコード。Lively-Web にある Smalltalk-72 のエミュレーターで動かしてみました(ちょっと改変しました)。目は % 、⇨ は ?、指さしは " 、← は _、評価(!)は \ で入力可能です。 pic.twitter.com/GvWGf9bKNU
— sumim (@sumim) 2015年5月15日
さらに組み込みのエディタなども活用して Smalltalk-72 を本格的に体験したいということであれば、Squeak Smalltalk に関する知識がそれなりに必要にはなりますが Squeak3.2 で動く Smalltalk-72 エミュレーターがお薦めです。今回は後者を使います。
さて、Smalltalk-76 が Object のインスタンスで true, false, nil を表わしていたのだから、それ以前の Smalltalk-72 でも同じだろう…と安直に考えていたのですが、調べてみるとどうやら違うようです。そもそも Smalltak-72 では Smalltalk-76 と違って、true, false へのメッセージ送信による条件分岐処理に戻って(?)いますので、この時点でもう間違っています。^^;
Smalltalk-72 では「 真偽値を返す式 ⇒ (真の時に評価する式) 偽の時に評価する式 」で条件分岐を記述します。(この ⇒ は ? で、後の is? で使う ? は ~ で入力できます。! は Lively-Web版では \ 、Squeak3.2版では上方向カーソルキーです。)
ちなみに Smalltalk-72 では、メソッドの定義はパターンマッチで記述された文法の定義のようなものなので、if オブジェクトに対するメッセージ送信の形で ALGOL系の if-then-else も定義可能で、実際にそうした記法を可能にするコードもエミュレーターには含まれています。
これを踏まえて true, false ついでに nil がどのように扱いかを調べてみましょう。Smalltalk-72 ではオブジェクトに自身が属するクラスを訊ねるためには is? というメッセージを送信します。
true は atom と称したシンボルオブジェクトに属します。一方、false は is? メッセージに対して自身を返している、あるいは false がクラスであるかのように振る舞いますが、実際には false は falseclass のインスタンスです。
show falseclass で定義を確認すると、どうやら is や is? メッセージを受け取った際に自身を返すように、さらにご丁寧に本来 偽(false) であるはずの false is false が 真(true) を返すようなパターンマッチを用意してまで特殊な振る舞いがコードされているようです。
false 向けのいくつかのメソッドはプリミティブ(CODE 11)として記述されておりユーザーからは見えないのですが、is やそれに続いて ? がメッセージと送られた場合にどういう振る舞いになるかは Smalltalk-72 自身で記述されており、false is? が falseclass ではなく false を返していることがこの定義を見ると分かります。
この特殊な振る舞いの定義を削ってしまえば正しく応答してくれるはずなのですが、ただ is メソッドはクラスごとに実装する必要があるようで、これを欠いてしまうと is? メッセージに対して正しい反応ができないようです。
そこで、falseclass の特殊な振る舞いの is の定義をいったん削除して他の通常のクラスのような is メソッドを定義してみました。(残念ながら、組み込みのエディタを用いたこの操作は、なぜかマウスクリックをエミュレーターが検出しない Lively-Web 版ではできません。あしからず。)
これで false も is? メッセージに嘘をつかなくなりましたので再び試してみます。
▼まとめ
Smalltalk の状況に限れば、察するに true, false にメッセージを送って条件分岐をするなど必要がなければ、それぞれにクラスは無くても大丈夫で(Smalltalk-76)、もしその必要があっても、false 以外は真扱いにするのであれば、false クラスだけで用は足りる(Smalltalk-72)ということになりそうです。
さらに、true のみに真の振る舞いをさせるなら True クラスも必要で、加えて真偽値の振る舞い(メソッド)を増やしたり、その際にテンプレートメソッドパターンを活用したいとき、あるいは抽象データ型OOPを限定的にでもサポートすることを考えた場合は Boolean クラスも用意しておくのが便利(Smalltalk-80)なようです。