オブジェクト指向再考(メッセージ4、メソッドと関数3)


今月も月末に向かって地味に忙しくなって更新が遅れてしまいました・・・。

 前回と前々回に分けてダブルディスパッチについて説明しました。前回の繰り返しになりますが、この考えをさらに進めると複数のオブジェクトに対してポリモーフィズムを効かせればどうなるか?という話になります。一般化ですね。もっとも残念ながら、3つ以上のオブジェクトに対してポリモーフィズムを効かせる具体例が思いつきません。まぁ、『3者面談をやった時の先生と生徒と保護者の性格別の対応策』というのも考えられなくはないですが、今まで3つ以上のオブジェクトに対してポリモーフィズムを働かせた実戦経験はありません。ただ、この一般化-マルチディスパッチとかマルチメソッド-というアイデアを頭の隅に置いておくといつか使える日が来るかもしれません。

n体のオブジェクトに対してポリモーフィズムを働かせる。

と考えた場合、『n=0の時はどうなるか?』という考えが湧いてくるでしょう。そうです。n=0の時はメソッドに関係するオブジェクトが無いと考えられます。従来の関数がそれにあたります。
C++は自然に関数が定義できるのですが、JavaやC#は悪名高いpublic staticメソッドを使って関数を定義します。

以上が前回の復習になりますが、さて『関数が必要となる場合』の具体例をあげましょう。こちらもC++のライブラリからになりますが、一般的に以下のようなアルゴリズムの実装はオブジェクトと関係ないということで関数(正確にはテンプレート関数)で実装されています。

・合計値を求める
・ソートを行う
・検索を行う。

JavaやC#のみ知っている方はこの事実に驚かれるかもしれません。が、アルゴリズム+データ構造=プログラムという古典を知っている方なら逆に納得してもらえるかもしれません。つまり、データ構造に対して独立した手続き-アルゴリズムを考えた場合、関数での実装が上手くいくということになります。
それでもまだピンとこない人がいるかもしれません。そういう方はジェネリックプログラミングについて調べてみるのも良いかもしれません。この連載でジェネリックプログラムを扱うのは話が発散するので今は避けますがJavaにしてもC#にしてもジェネリックプログラミングをサポートしていますが、オブジェクト指向プログラミングとは必ずしも相容れるものではありません。

ということで関数(手続き)は、アルゴリズムの実装と相性がいいという話をしました。他には、実はかのstaticおじさんと揶揄されているみながわさんが指摘した、通常の手続きにも関数が使えるという話をします。
さて、みなさんがあるアプリケーションの実装時に、あるURLで指定されたHTMLファイルを取得する必要があった場合、以下の2つのライブラリのうちどれが便利に使えるでしょうか?

関数 gethtml を使う。
URLクラス、URLConnectionクラス、InputStreamクラス、BufferedReaderクラスを使う。

もし、各種クラスを使う方が便利と思うなら、あなたを『オブジェクト指向おじさん』に認定しましょう。
gethtml関数の方が良いのは自明でしょう。もし疑問があるのならコメント欄に質問してください。

さて、次回は変数の共有との関係で関数とメソッドを比べてみます。
2016-04-22 | コメント:0件



オブジェクト指向再考(メッセージ3、メソッドと関数2)

 連載も5回目になりましたが、まだモチベーションが維持できています。前回はダブルディスパッチの説明と共にメソッドと関数について本質的な違いはないという説明をしましたが、もう少し突っ込んで話をします。
もともと
object.method();
という表記は、methodがobjectのクラスに属しているという意味合いが強いかと思います。言葉を変えるとクラスとはデータと、そのデータを扱うコードが合わさったものという発想です。これはこれで一つの考え方で構いませんが、問題は全てをメソッドにするのも間違いだということです。その一例がダブルディスパッチということになります。以下のように加算演算子+を考えた場合、

object1 + object2

+演算子は
 1.object1に所属するのか?
 2.object2に所属するのか?
 3.両方か?
 4.どちらでもないか?等々
1-4の内どう考えるのが自然なのか?という問題に突き当たります。Javaは演算子のオーバーロードを禁止することによりこの問題から背を向けました。つまりこの問題を避けているとも言えます。ちなみにこのような仕様を見たときにJavaは補助輪が付いた自転車のような印象を受けました。C++は『どちらにも属さない』書き方ができます。正確にいうと『object1に属する』の書き方も場合によってはできますが、例えば、

10 + object

のような場合は、『どちらにも属さない』書き方でしか書けません。つまり関数を使ってオーバーロード演算子の定義を行います。C#はある意味『どちらにも属さない書き方』で書きます。つまり、多くのオブジェクト厨が忌嫌うpublic staticメソッドでオーバーロード演算子の定義を行います。

ダブルディスパッチの話を終える前にマルチディスパッチ(マルチメソッド)の話を軽くします。前回、2つのオブジェクトに対してポリモーフィズムを働かせる仕組みをダブルディスパッチと説明しましたが3つ以上のオブジェクトに対してポリモーフィズムを働かせる考え方もちろんあり、マルチディスパッチまたはマルチメソッドと言います。マルチメソッドをいつ使うのか?と言われたらそれはそれで考え込んでしまうのですが、それでも、メソッドが何処かのクラスに属するという考え方に固執するとこのようなパラダイムを受け入れることは難しいでしょう

さて、このようなある種の一般化を行った後は、今度は全くクラスに属さない手続き=関数という存在も自然に受け入れることができるかと思います。つまり、

y = sin(x);

のような式において、文字通りの関数、sinはどのクラスにも属さないと考えた方がよいでしょう。「Mathクラスに入れたらいいだろう」と反論を受けそうですが、実際にsin関数はJava厨が忌嫌うpublic staticで定義されており、C++でも関数として定義されています。先の演算子の例でも出てきましたが、public staticメソッドとは手続型言語での関数定義に相当するものになります。ちなみにかのεπιστημηさんは私との議論で、

Java/C#はどのクラスにも属さないメソッドが作れないため、
当たり障りのないクラス(たとえばMathあるいはModuleC)をでっちあげてそいつに押し込まざるを得ない。
「いかなるクラスにも属さない」という"思い"を表現できません。
僕にはそこが(C++と比べて)不自由に感じます。

というふうに本質的に関数であるものを仮のクラス(Math)に入れることには難色を示されています、これについては私も同感です。

さて、『そんなに関数が重要ならsinとかではなくもっと幅広く使える例を出せ!』と言われそうです。その答えは来週に行うということで。
2016-04-10 | コメント:0件



オブジェクト指向再考(メッセージ2、メソッドと関数1)

 なんやかんやでかろうじて連載も4回目になりまして、モチベーションも維持できてよかったです。語学留学もあと一カ月で、最近、仲良くなった大学生にC++を教えています。といっても最初はポインタを教えていたのですが次にリファレンスを教えることになって、思わず、「混乱の元だからリファレンスは最初は無理して覚えなくてよくてポインタを先に覚えましょう。」と言ってしまった。もっとも「リファレンスの方が簡単だから軽く覚えてガッツリポインタを勉強しよう。」とも言ったが、この連載のターゲットは『オブジェクト指向しか知らない世代』としていますが、JavaやC#しか知らない方たちはポインタの概念が理解できているか怪しい限りです。興味深いのは、私の半径3メートルの範囲、クパチーノ近辺ではC++をマスターすることがステータスになっているようです。

 という訳(?)で今回はメソッドと関数について話します。なぜか知らぬがネットの界隈ではどうも、

メソッド>>>どうしようもない壁>>>関数

という図式が成り立っているようです。でこのどうしようもない壁なんて無いというのが今回のテーマです。もっとも1回で説明するのは難しいので何回かに分けて説明します(しかも飛び飛びになるかも)。

先ず、最初に以下の2つの表現

object.function();

function(object);

ですが、C++でみると生成される機械語コードは同じになります。例外はfunctionが仮想関数(ややこしいので以下、オーバーライドメソッドと呼びます)の場合になります。オーバーライドメソッドを除いて、C++でみると上記の2つのコードは同じということになります。『カプセル化があるだろ』という突っ込みがあるかもしれませんが、それは他のキーワード(friend)を使うことによって同等にできます。
もし、あなたには『どうしようもない壁』が見えるというのならどうぞ具体例とともにコメントをください。

ということで、ADPでは上記の2つのコードは同じように解釈されます。というか上のコードは下のコードとして解釈されます。『同じなら一つの書き方で良いだろ!』とお叱りを受けそうですが、表記上の重要な違いがあります。つまりobject.function()の記述はメソッドチェーンを実現できます。

object.function1().function2().function3();

もしこれを関数の形式で書くと

function3(function2(function1(object)));

となりますが、どちらが良いかは一目瞭然だと思います。ただ、これはあくまでも表記上の話でもしこれがどうしようもない壁というのならオブジェクト指向というのは表記上の問題ということで話が付きますし、そもそもメソッドチェーンの見た目はもはやメッセージパラダイムとは異なるでしょう。

さてfunctionがオーバーライドメソッドの場合の話ですが、簡単にオーバーライドメソッドについて話ますと、呼び出されるメソッドが実行時に決定される点です。C言語の関数は呼び出される実体がコンパイル時に決まりますが、オーバーライドメソッドの場合は実行時にオブジェクトの型によって呼び出される実体が決まります。

object.add(1);

とあった場合、objectがInteger型ならIntegerクラスのaddが呼び出され、objectがFloat型ならFloatクラスのaddが呼び出されるということになります。これはポリモーフィズムと呼ばれるもので、オブジェクト指向病にかかっている人たちはまさにポリモーフィズムが手続き型言語との差別化を図っていると信じています。ポリモーフィズムについては連載の後の方で詳しく説明しますが、関数ポインタを使うとこによりCやアセンブリ言語でも同様の機能を実現できます。こう言うと『ならアセンブリ言語で全部書けや!』と意味不明な切り返しをされるのですが、それに答ますとLinuxの記述では主にCが使われているというのは有名な話ですが、でデバイスドライバの実装等ポリモーフィズムと同様のことが行われています。つまりCでもある程度はオブジェクト指向を実用レベルでシミュレートできるということです。ちなみにADPはC++で記述しておりポリモーフィズムもバリバリ使っていますがパフォーマンス上の理由からCでリライトしようかと思案中です。

さて、話が横道にそれましたので元に戻しますとaddの例ですが、何か変なことに気付かないでしょうか?例えば以下の例はどうでしょうか?

object1.add(object2); // ・・・・・(A)

このaddメソッドですが、object1に対してはポリモーフィズムが効きますがobject2に対してはどうでしょうか?JavaやC++ではobject2にはポリモーフィズムは働きません。まぁ関数の例でもそうでしたが引数にはポリモーフィズムは働かないと考えてもしようがない面もありますが、もし引数にポリモーフィズムが働かないのが当然だというのなら(A)は以下の例と動作が異なることになります。

object2.add(object1); // ・・・・・(B)

加算(add)というのは通常交換法則が成り立ちますのでもし(A)と(B)の動作が異なるということならその実装は不完全としか言いようがないですね。ので通常addがポリモーフィズムであるならそれはobject1,object2双方に対してポリモーフィズムでなければならないことになります。2つのオブジェクトに対してポリモーフィズムを働かせることをダブルディスパッチと呼びます。私はダブルディスパッチについて16年程前に『More Effective C++』で知ったのですが、以前、といっても2,3年程前にJavaが得意で自称オブジェクト指向をマスターしている彼が『オブジェクト指向をマスターしている人は日本にはほとんどいない。俺を除いて』と言っていたので『ダブルディスパッチって知っている?』と聞いたら『なにそれ?』と返されたことがある。ということで知ったかのJava厨の検出にはダブルディスパッチは今のところ効果的かと思われる。ちなみにC#4.0以降の場合、dynamicキーワードを使うことにより(A)の記述でもobject2に対してもポリモーフィズムを働かせることができるのでC#プログラマにダブルディスパッチの技を繰り出すのは返り討ちにあう可能性があるので注意したい。話を戻してC++やJavaの場合はかの有名なデザインパターンの一つビジターパターンがダブルディスパッチの実装方法の一つとなる。
ダブルディスパッチの使いどころですが、二項演算子でポリモーフィズムを使いたいとき(もっともパフォーマンス上の問題があり二項演算子でポリモーフィズムは使わない場合が多い)や2つの物体の衝突を計算する場合(More Effective C++)があるでしょう。ADPではユニフィケーションの実装にビジターパターンによるダブルディスパッチを使用しています(これがしたいが為にC++を使ったがもっと効率的な記述をしたいからCで組みなおそうかと考えている)。

つまりこういうことになる。

object.method()



function(object)

において表現上の違いやプログラミング言語のサポート状況による違いはあるが本質的には両者を同等物とみて構わないということである。つまりどうしようもない壁はないということになる。

長くなったので続きは来週に持ち越します。
2016-04-03 | コメント:0件
Previous Page | Next Page