こんにちは、このコースでは私は主に オブジェクト指向プログラミングについて話します。
そしてオブジェクト指向言語でのディスパッチと
遅延束縛について学びます。
このコースでは Pharo を例として使います。
ありがたいことに Pharo はうまく実装されています。
次のクラスでは、その大きな潜在力のある 一般化について話します。
では始めましょう。
真偽値(Boolean)を見てみましょう。
真偽値は Pharo では
本当に最も基本的なものです。
& や | や not のような 先行評価の真偽値演算子があります。
or: や and: のような遅延演算子は 必要な時に引数を評価します。
条件分岐もあります。 他のコースで見ることになるでしょう。
良く実装されていますが 驚異的なことや特別なことは何もありません。
先週、練習問題を出しました。
3つの質問がありました。
not をどうやって実装するのか
or をどうやって実装するのか
そして最も重要なことですが これらの練習問題のゴールは何なのか？
最初の2つの問題に答えます。 そして次のコースで
最後の問題に答えます。
練習問題は…
false に not メッセージを投げたら true が返ってきます。
true に not なら、falseが返ってきます。
オブジェクトがあるとして どうやってこれを実装しますか？
まずはいくつかヒントをあげます。
正解では条件分岐を使いません。
大部分の人は条件式を使った答えを見つけるでしょう。
しかし、私の答え、Pharo の実装では
全く条件分岐は使いません。
条件式も。
つまり、正解には if はありません。
少し考えてみてください。 何かアイデアはないですか？
普通はこの手のヒントだけで答えは出てきません。
2つ目のヒントです。 答えは3つのクラスを使います。
Boolean クラスがあります。
これは抽象クラスです。
True クラスと False クラスがあります。
真偽値オブジェクトの true は True のシングルトンインスタンスです。
違いがわかりますか？
true インスタンスは小文字で始まって
False クラスは大文字の F で始まります。
そして false は False のシングルトンインスタンスです。
ダイアグラムで可視化すると
true は True のインスタンスで false は False のだと目に見えて解るでしょう。
理屈の上では、このヒントがあれば 答えは明らかな筈です。
実際あなたに解ったかどうか 私には判らないので
もう少し考えていただきます。
答えは最後にします…
今すぐには教えません。 考えてみましょう。
「選択」をオブジェクト指向言語で どうやって表現しますか？
同じインターフェイス（メソッド）を持つクラスを
いくつか定義して
インスタンスにメッセージを送ることで 選択を表現します。
ここに例があります。 x open と入力すると
x に関連付けられた正しいメソッドを 選択します。
つまり、ファイルであればファイルを開き ウィンドウであればウィンドウを開き
ツールであればツールを開きます。
つまり x のクラスに基づいて メソッドが選択されます。
さあ、このヒントでどんな答えが出てきますか？
これと同じように実装するのです。
つまり、False クラスの not メソッドを実装します。
この場合、true を返します。
True クラスの not メソッドを実装します。 false を返します。
ダイアグラムでは、こんな風になります。
この方法では、明示的な条件分岐がないことが わかるでしょうか？
この場合、if は全く使いません。
どうやって動くのでしょうか？
こんな風に動きます。 not メッセージを送ると
not メソッドをどこから探すでしょうか？ レシーバーのクラスです。
true は True のインスタンスです。
なので、ここのこのメソッドが実行されます。
そして結果は false になります。 ちゃんと動きます。
では false インスタンスに not メッセージを投げます。
どこを探しますか？ False クラスです。
この not が実行されて true が返ります。
これで真偽値を実装して
真偽値の否定を2つのメソッドで
条件分岐を使わずに実装しました。
スーパークラスの実装を見ることもできます。
Boolean クラスは抽象クラスです。
2つのサブクラスがあって サブクラスが必要な演算子を実装しています。
Pharo では
Boolean の 抽象メソッドとして not を表現しています。
self subclassResponsibility を使って。
Pharo について言うことで それを一般化して欲しいのです。
ここまでで
Or の振る舞いをどう表現するか 理解できたはずです。
これを書く時間を与えます。
考え方としては
引数を使ったメソッドを定義して
値に応じて
正しい結果を導きます。
条件分岐を使えばそれで十分だ と考えることが多いでしょう。
違います。これが要点です。
もう一度言います。 or を実装するのに条件分岐は必要ありません。
考える時間を10秒あげます。
準備してきたはずですよね。
私の答えは、Boolean 抽象クラスに
or を抽象メソッドとして定義します。 結構。
そして False クラスでは ここに書いた通りです。
レシーバーは False に属しています。 何を返しましょうか？
true であれば、true を返します。false であれば、false。 何であっても、それ自体を返します。
つまり、引数を返します。
ここに False クラスでの or の実装があります。
引数を返します。
ここでやった通りです。
同様に
True クラスを見ると
ここで説明されています。
true をレシーバーとして or を送ると
レシーバーを返します。
つまり、true を返します。
ここを見ればわかる通り ここでもまた条件分岐を使っていません。
メッセージ送信を使っただけです。
より綺麗な方法として Pharo ではどう書かれているでしょうか？
true がメッセージのレシーバーだと知っています。
なので、true と書く代わりに self と書きます。
定義を読めば self とあるのを見て
なるほど。 レシーバーが true だから
同じことか と解るでしょう。
では、もう一度可視化しましょう。
引数 alpha をつけて true オブジェクトに
or メッセージを送ると
or の定義をここから探します。
そして self を返します。 したがって true です。
引数 alpha をつけて or メッセージを送ると
False クラスから探します。
そしてこの実装に辿り着きます。 alpha を返します。
こうして真偽値表を実装します。
覚えておくべきことは
ここで実装した回答では、条件分岐や
ループのような
明示的な条件命令を全く使わないで
レシーバーに決めさせることです。
つまりメッセージを受け取った Boolean オブジェクトに
正解を見つけてもらうことです。
独裁的に決めてしまうのではなくて
どこか他の場所で見つけるのが原則です。
それが OOP の基本原則です。
このレッスンの最初でほのめかした経験則です。
求めるな、命じよ。
つまり条件分岐を表現するのではなく
命令を与えるのです。
それが OOP の重要な鍵の1つです。
もう1つは レシーバーに決めさせることです。
レシーバーに命令を与えて そのレシーバーが自分の知識をカプセル化したまま
正しい決定をするのです。