ビスケットのあれこれ

ビジュアル言語ビスケット(Viscuit)に関するあれこれを書いてゆきます.

オブジェクト指向の問題点

2016-05-10 12:28:06 | 1
オブジェクト指向プログラミングを神格化するような記事が流れてきたので,僕が知っている問題点について書いてみたいと思います.僕がまだ学生だったころは,オブジェクト指向の評価もまだそれほど定まっていなくて,オブジェクト指向の次はどんなパラダイムが出てくるかとか普通に学生レベルで議論していたものですが,ここまで強大になってしまうとそれを打ち負かそうなんて気にはならないのでしょうか.僕にはオブジェクト指向が普遍的な真理という感じは全然しなくて,ここまで使われてる理由は,現実的なテクノロジーで大きなシステムを作らなければならない必要性のほうを優先した結果であると認識しています.オブジェクト指向がその後の25年ほどもずっと安定してその地位を保てるほど素晴らしいものとは思えないのです.

以下で上げる問題点は,個別に解決している研究はあったりしますし,僕も論文を書いたりしましたけど,実際の言語に導入されていなければあまり意味がないので,問題点として指摘させていただきました.論文じゃないので,軽い気持ちで読んでいただければと思います.

1)メッセージの流れが見えない
 オブジェクト指向においてメッセージのやり取りほど重要なものはないのに,そのやり取りの様子がプログラム上で表現できていません.メッセージの送信は,繰り返しや条件分岐などの制御文によって作られた関数呼び出しの副作用として間接的に表現されます.一方受信の方はもっと見えなくて,例えばその時のオブジェクトの内部状態によって受信できるメッセージの種類が変わるはずなのに,そういった制御や抽象化ができません.最初に初期化のメッセージを受信した後,処理のメッセージをいくつか受信して,終了のメッセージを受信する,といったプロトコル的な記述もできません.

2)オブジェクトのつながり具合が手続きでしか表現できない
 次に大事なのがオブジェクトのつながり具合です.たとえばMVCという3つのオブジェクトの集まりで一つの部品を作るような場合があります.プログラムは,M,V,Cそれぞれが同じ相手とつながっていることが前提でつくられます(たとえば,Mが知っているVと,Cが知っているVは同じものである,といったことです).しかし,このつながり具合が正しいかどうかを保証する方法がありません.木構造をオブジェクトで作るとき,自分の子供は全員自分を親として見ているか.こういったつながりを手続きで頑張って作るので,間違えが入る可能性があります.間違えをできるだけ入らないようにするために,オブジェクトの外側からは内側を触れないようにカプセル化しますが,そういう制約はオブジェクト指向自身が自由度が高すぎる,逆に言うと何も保証しない貧弱であるから必要なのです.


3)ユーザレベルでの部品化再利用に全然なっていない
 僕が素晴らしいプログラムの部品化再利用の例だと思っているのがUNIXのパイプラインです.これが通じる人はどれくらいいるのかな.コマンドの出力が次のコマンドの入力になるようにパイプでつないで表現するものです.パイプが人間にとってすごく使いやすい理由は,そこを流れるデータが人間に見える形式だったからです.見えるから次のコマンドにどんな処理をさせればよいか考えやすくなります.何か動きがおかしければ,途中に流れている文字列を見ればわかります.典型的な入力データをエディタで作ってもよいですし,途中の処理が特殊すぎてわざわざプログラムで処理しなくてもよいような場合,人間がエディタ上で手作業で編集してもよいわけです.テキストデータという共通のデータ形式の上で人間とコンピュータが協調して問題を解いていたという感覚がありました.

こんな素晴らしい仕掛けが使われた時代はそんなにコンピュータが速かったわけではありません.それでもバイナリーではなくテキストの10進表記で数を表すという,実に無駄な方法が人間の利便性を優先して使われていたということに敬意を表します.

1次元にコマンドを並べて処理を順に行うような単純な処理だけですむ時代は終わりました.パイプに代わるもっと複雑なコマンドの組み合わせ方を発明しなければなりません.パイプは一方向の情報の流れでしたが,情報は双方向の流れを許すべきです.

僕はUNIXにX-Windowというウインドウシステムが入ってきたとき,次世代のパイプが発明されるのかとワクワクしてましたが,結局まだ誰もそれを発明できていません(僕が知らないだけであるのかもしれません).部品化はオブジェクト指向にとってかわられ,プログラムの中は閉ざされ,人間は遠ざけられました.

北大の田中先生のIntelligentPadにその可能性を感じましたが,結局はオブジェクトの合成という非常に難しい問題がその前に立ちはだかりました.

そうこうしているうちに,WWWの大爆発が起こって全部吹っ飛びました.


4)知識表現が手続き側に偏っている
 プログラミングという処理っぽい呼び方ではなく,もう少し広い意味を込めて知識表現と呼ぶことにします.プログラムはもちろん知識表現の一部です.たとえば,EXCELの表も知識表現ですし,構造をもったデータは知識表現です.オブジェクト指向の何が悲しいかというと,構造をもったデータを直接作り出すことはできないということです.手続きを使って変換するしかありません.たとえばLISPではコンピュータが処理するデータ構造を人間が直接入力することができます.そういう人間とコンピュータの仲の良い関係がオブジェクト指向では消えてしまってます.

5)オブジェクト指向は人間をだましている
 オブジェクト指向で作られた典型的なグラフィカルなプログラムを考えましょう.オブジェクトには内部状態があります.その内部状態を更新させることが唯一の計算です.その内部状態を人間にみせるためのプログラムと,人間が見ているもの(画面)に対するマウスやタッチの操作を解釈して内部状態を更新するためのプログラムがあります.これは上の例ででてきたMVCということなんですが.だましているといったのは,Mは内部に存在しますけれども,Vがどう作られたかで見え方が違ってきます.同じ見え方でもMの中身が違っていたり,同じ中身でも見え方が違っていたり,ということが平気で起きます.真逆はLISPです.LISPはコンピュータが見ているメモリーの構造と人間が見ているS式とが1対1の関係になるので,つまり人間とコンピュータが同じものを見ているという安心感があります.オブジェクト指向はコンピュータと人間が同じものを見ている感がしません.コンピュータがすごく頑張って人間にすごいものを見せている(もしくはだましている)という感じです.

 こうなっちゃった理由は,コンピュータに課せられた役割が,これまでプログラムを作る人と使う人とが一体となっていた時代から,作る人と使う人とが明確に分かれた時代に変わってきたということなのかもしれません.UNIXのテキストは人間にわかりやすいといっても,ビットマップのテキストよりは見栄えがしないわけで,作る人と使う人が一緒なら我慢もできましょうが.逆に,使うだけの人にとっては,上手にだましてもらったほうがいいわけですし,よりかっこよくだました方がお金が儲かるという事情もあったのでしょう.


6)オブジェクト指向は貧乏
 オブジェクト指向が活躍した時代はメモリーが非常に少なくどう節約するかが重要でした.なのでよく使われていた例は,列というものがあって,その列をどのように扱いたいかによって実行の効率が変わるので,使用法に適した実装を選んで効率をよくすることができる.しかしアルゴリズムはそのデータ構造の影響を受けないように計算方法だけ書けばよい.そんなことが言われていた時代でした.つまり,一度書いたアルゴリズムはいろんなデータ構造に対して,効率の良い実行ができるというものです.

しかし今は,メモリーもふんだんにあって実行速度の差もそれほど重要ではなくなってきたので,そんな実装方法に依存しないアルゴリズムという貧乏なことを言わなくてもよくなりました.万能なデータ構造を一つ決めて,アルゴリズムは全部その構造に対して作ってしまえばよいのです.LISPみたいなものです.ただし,LISPの時代から何が変わったかというと,扱うデータが木構造のように単純ではなく,グラフ構造になったこと.一つのスレッドではなくマルチスレッドでかつ分散になったこと.オブジェクトが複雑に参照しあっているようなグラフ構造をLISPのS式のようなシンプルで汎用性のある記法で記述しなければならないということです.なのでS式的な今風の拡張が求められています.

7)関係を記述できない
 メッセージは単項演算なので,複数のオブジェクト間の関係を表現するのが苦手です.ある一つのオブジェクトに,起きろ,歩け,といった命令をするだけなら簡単です.それに対して,二つのオブジェクトが関係する命令,たとえば二人に対して,ぶつかれ,ならべ,という命令が苦手です.通常のオブジェクト指向では,関係するオブジェクトのどれか一つを特別に選んで,それに対するメッセージとして表現します.他のオブジェクトはそのメッセージの引数として渡されます.2)で言ったようなつながり具合を表現したければ,メッセージの受信側に選ばれたオブジェクトが責任をもって引数で渡されたほかのオブジェクトどうしをつながなければなりません.

Prologは関係を表現できます.というか関係しか表現できません.たとえば,A+B=Cという式があったとします.普通の言語ではA+Bの計算をして,Cのメモリーにその値を書き込むという解釈をします.AとBの値が決まっていてCは未定義だったり,Cの新しい値としてこういう計算をしました.それに対して,ここで表現した式は計算の方法ではなくあくまでも式というか変数同士の数学的な関係を表していると解釈できます,たとえばこの式があったとき,Prologでは「Bが20,Cが50のときAは何でしょう」という聞き方にもこたえることができるのです.

計算の方向まであとで決められるくらいの自由度はともかく,オブジェクト指向がメッセージのやりとりにこだわっていると,関係を表現するのがすごく大変ということはわかっていただけたでしょうか.


オブジェクト指向の問題点のうち,ごく一部はビスケットの設計方針に影響を与えています.たとえば,コンピュータが裏で人間のために頑張るというのではなく,コンピュータと人間が同じものを見ている感というのを大事にしている部分などですが.人間とコンピュータの関係がどうあってほしいかといった思いも込められています.

最新の画像もっと見る

8 コメント

コメント日が  古い順  |   新しい順
SmalltalkでProgramを書いてみてください (中村聡)
2016-05-10 23:39:28
できればVisualWorksそれが難しいならGNU Smalltalkで最大限既存のClassを使って一本Programを書いてみてください。今の考えが瓦解して何が理想なのかきっと見えてくるはずです。
ObjectやMessageは決して現実の比喩ではありません。あくまで制御構造であり本質は再帰と同じまです。
言っている意味がわかりづらければWikipediaでSmalltalkの記事を読んでみてください。
返信する
オブジェクト指向について学びなおしてみては? (とおりすがり)
2016-05-11 00:41:17
挙げられている問題点の多くは、現代のオブジェクト指向設計において既に解決されています。
具体的な設計/実装手法について学びなおすことで、大半の項目が覆るはずです。

「契約による設計」
「開放閉鎖の原則」

この辺りのキーワードで検索されることをお勧めします。
返信する
補足 (中村聡)
2016-05-11 01:40:35
突き放ししすぎた気がしますので補足します。
SmalltalkにはtrueとfalseというObjectが存在します。trueとfalseは下記の様なMessageを与えることができます。

| result then else |

then := [ 1 ]. "[] は無名高階関数"
else := [ 2 ].

result := true ifTrue: then ifFalse: else.
result := false ifTrue: then ifFalse: else.

この時trueはthenを評価(#valueを送る)しresultを1にします。
falseは逆にelseを評価することでresultを2にします。
言い方を変えると"ifTrue: then ifFalse: eles"というMessageが現れた時resultをthenの結果にしたければtrueをelseの結果にしたければfalseを使えという事です。
これがObjectとMessageが制御構造として機能している典型的な例です。
もう一つ例を上げます。

| holder |
holder := [ :value | value printOn: Transcript ].
holder value: 1.
holder := NullValueHolder uniqueInstance.
holder value: 1.

こちらは"value: 1"というMessageが現れた時1を表示したければ表示用の無名高階関数を使い、無視したければNullValueHolderを使えという事です。この様にObjectはMessageが現れた時の動作を切り替えて制御構造を実現するための仕組みに過ぎません。Objectは現実の比喩でも無ければ、状態変化を必要とする物でもななく、関係を表現するためのものでも無いのです。特に状態変化はObjectに柔軟性を持たせるためのおまけのようなものです。例えばtrue, false, nil, []. を始め多くのObjectは初期状態から変化しません。true, false, nilについてはClassの違いだけで制御を実現しており内部に変数すら持ちません。このObjectとMessageという制御構造という観点でSmalltalkのClass群をみると実に良く出来ています。使用者は新しい振る舞いが必要な時、今までにないObjectとMessageの組み合わせを作るだけで新しい振る舞いを作ることができてしまうのです。
返信する
Unknown (原田)
2016-05-11 04:19:17
解説ありがとうございます.もちろんそういった感動は25年前にしました.そういう,シンプルな構造から複雑な仕組みが作り出せるのは面白いですし,これこそがコンピュータだという感じがしますよね.

で,他人がつくったものではなく,なんとか自分でもすごいのを作りたいと思ってて,ビスケットができましたが.その面白さに並ぶかどうかはともかく,かなり面白いですよ
返信する
Unknown (ほおお)
2016-05-11 07:37:52
>>中村聡さん
http://web.archive.org/web/20050305092826/http://marimpod.homeip.net/chomswiki/24
作者が、こう言ってるのですが。

それに、貴方が仰るオブジェクトとメッセージでプログラムを書くことの利点は何でしょうか
返信する
ほおおさんに対する回答 (中村聡)
2016-05-11 22:35:10
先に述べた意見はDaniel H. H. IngallsのDesign Principles Behind Smalltalkや
Alan C. KayのThe Early History of Smalltalkに反していないつもりです。


> それに、貴方が仰るオブジェクトとメッセージで プログラムを書くことの利点は何でしょうか

Messageの再利用と結合そして拡張性です。

| center result |

center :=
[ :edge1 :edge2 |
( edge1 + edge2 ) / 2.
].

result := center value: 1 value: 3.
result := center value: 1 @ 1 value: 2 @ 2.

まず再利用ですが、上記の例では中央座標を算出するMessageを一次元の数値と二次元の数値で共有しています。非OOPLなら一次元用と二次元用それぞれcenterを用意する必要がありますが中央座標の算出を1つだけにしてしまうことが出来るのです。また、一次元、二次元だけでなく高次元の数値や数列、将来現れるであろうあらたなClassのObjectに対しても同じMessageを再利用する事ができます。

someObject ifNotNil: [ :value | ].
Continuation currentDo:
[ :continue |
someObject ifNotNil: continue.
].

次に結合ですが上記例の#ifNotNil:の様に同じProtocolに従ったObjectと手続き(Method)は例え製造元が異なっても組み合わせて機能させることができます。非OOPLでは提供元が異なる機能同士を組み合わせられることは少いと思います。

| reader result |
reader := ReadStream on: #( 1 2 3 ).
result := reader next.

最後に拡張性ですが配列をReadStreamに変換している上記の例の様に既存のClassのProtocolを拡張するObjectを利用して既存のClassが対応していないMessageに適合させることができます。非OOPLではおそらく同じ概念はないでしょう。
返信する
Unknown (xxxxx)
2016-05-21 12:06:02
UNIXのパイプ利用はユニケージ開発手法が知られています。速いです。

xxxxx yes "1" > i

xxxxx wc -l i
135613440 i
xxxxx time cat i | awk '{print $1++}' | awk '{print $1++}' | awk '{print $1++}' > j

real 4m44.736s
user 13m4.854s
sys 0m2.228s
xxxxx wc -l j
135613440 j
xxxxx
返信する
がんばれ!! (viscuitのファン)
2016-06-28 22:16:28
オブジェクトの合成ってモナドの合成に近いのかな?
だからってモナドが人に近いかどうかはわからないけど
エクセルの循環参照とviscuitの動きは確かにちかいですね!
返信する

コメントを投稿