Buzzurlの中の人日記
なぜオブジェクト指向は嫌われているのか?
ざっと見たところ「設計が難しいよね」っていう意見が多いように思う。
このスレはたぶんプログラマ板だから想定されているオブジェクト指向言語はJava/C++/C#とかかな。
ここで言う「設計」というのはクラス設計のことだと考えていいだろう。もうちょっと大枠の設計の話になるとプログラミング言語はあんまり関係ないから。
んで、「Java/C++/C#はクラス設計が難しいから嫌われるよね」とする。
クラス設計が難しいのは色々考えると自明に思えるので、だから嫌われるんだとすると、Java/C++/C#のオブジェクト指向はそもそも解いている問題が不適切だった=設計が間違っていた、ということになるかなぁと思う。
なぜクラス設計が難しいのが自明かの説明を試みる。
Java/C++/C#のオブジェクト指向的機能というのは、数学的なモデルとしては「包含関係を持つ集合をプログラマが任意に定義できる機能」と言える。
包含関係というのはaggregationのことじゃなくて、集合としての包含関係なので、汎化/特化のことだ(つまり継承だ)。
例えば「FileInputStreamはInputStreamの一種である」とか、UNIX的なファイルの抽象化:「open/close/read/writeできるものは通常ファイルもデバイスもパイプもソケットもみんなファイルである」とかのことだ。
隠蔽も継承も多態も、みんなこれをプログラミング言語として自然に表現するための機能だ。
数学の集合の記述風に(いい加減な書き方だが)こんな風に書くのは
File = { x | x.open() / x.close() / x.read() / x.write() できるもの }
プログラミング言語では(擬似的にJava風に書くと)
に相当するだろうし、
Fileの一種であり特別な場合であるPipeは集合としてはFileの部分集合である。集合風には
Pipe = { x | x ∈ File かつ x は パイプ }
Pipe ⊆ File
こう書けるし、プログラミング言語では例えば
こうなるだろう。
クラス定義 ≡ 集合を定義
であり、
インスタンス作成 ≡ 集合から元を一つ取り出す
であり、
継承 ≡ 部分集合を定義
である。
Pipe ⊆ File (PipeはFileの部分集合である)なので、Pipeの元はいつもFileの元であり、PipeインスタンスはいつもFileポインタに代入可能である。
File f = new Pipe(/**/);
一方で、興味深い集合を定義するのはすごく難しい。
同じ問題を解決するにしても、それを解決するための一連の集合を定義するときには無数の選択肢がある。(「読み書きできるものはなんでもファイル」という素晴らしい抽象化の上でもこれは難しい問題だ。例えば↑のFileインターフェイスの各メソッドはどんなシグネチャにすべきだろうか?)面白い集合定義もあればつまらない集合定義もある。例えば数学で言うと群とか、結合法則と単位元と逆元の存在を仮定するだけというすごくシンプルなクラス設計でありながらものすごく色々な興味深い性質を持つ。しかしみんながみんなアーベルやガロアみたいに興味深いやり方で集合を定義できる、とは思えない。
うん。全然説明になってないな。どうみてもトートロジーです。本当にありがとうございました。
昔はオブジェクト指向大好きだったんだけど、PerlやJavaScriptのような関数をリテラルとして扱える言語を使うようになったら、この手の抽象化のほうがオブジェクト指向が取り扱う抽象化よりもよりシンプルでより強力なんじゃないかと思うようになった。
関数をリテラルとして扱うというのは、関数を関数の引数に渡したり、戻り値として返したり、変数に束縛したり、定数として用意したりすることだ。
数学モデルとしては、「関数を集合の元として扱うことを許す」みたいなことを意味する。そしてこれは数学的には自然というかよくやることだ。(例:実数係数の多項式全体からなる集合Fが自然な加算と定数倍についてベクトル空間となることを証明せよ)
#関係ないけどクロージャとかは数学的にはどういうものなんだろう。普通の写像とは違うような気がするけど。誰か偉い人教えてください。
ざっと見たところ「設計が難しいよね」っていう意見が多いように思う。
このスレはたぶんプログラマ板だから想定されているオブジェクト指向言語はJava/C++/C#とかかな。
ここで言う「設計」というのはクラス設計のことだと考えていいだろう。もうちょっと大枠の設計の話になるとプログラミング言語はあんまり関係ないから。
んで、「Java/C++/C#はクラス設計が難しいから嫌われるよね」とする。
クラス設計が難しいのは色々考えると自明に思えるので、だから嫌われるんだとすると、Java/C++/C#のオブジェクト指向はそもそも解いている問題が不適切だった=設計が間違っていた、ということになるかなぁと思う。
なぜクラス設計が難しいのが自明かの説明を試みる。
Java/C++/C#のオブジェクト指向的機能というのは、数学的なモデルとしては「包含関係を持つ集合をプログラマが任意に定義できる機能」と言える。
包含関係というのはaggregationのことじゃなくて、集合としての包含関係なので、汎化/特化のことだ(つまり継承だ)。
例えば「FileInputStreamはInputStreamの一種である」とか、UNIX的なファイルの抽象化:「open/close/read/writeできるものは通常ファイルもデバイスもパイプもソケットもみんなファイルである」とかのことだ。
隠蔽も継承も多態も、みんなこれをプログラミング言語として自然に表現するための機能だ。
数学の集合の記述風に(いい加減な書き方だが)こんな風に書くのは
File = { x | x.open() / x.close() / x.read() / x.write() できるもの }
プログラミング言語では(擬似的にJava風に書くと)
public interface File {
public void open(/**/);
public void close(/**/);
public void read(/**/);
public void write(/**/);
}
に相当するだろうし、
Fileの一種であり特別な場合であるPipeは集合としてはFileの部分集合である。集合風には
Pipe = { x | x ∈ File かつ x は パイプ }
Pipe ⊆ File
こう書けるし、プログラミング言語では例えば
public class Pipe implements File {
public void open(/**/){ /*実装*/ }
public void close(/**/){ /*実装*/ }
public void read(/**/){ /*実装*/ }
public void write(/**/){ /*実装*/ }
}
こうなるだろう。
クラス定義 ≡ 集合を定義
であり、
インスタンス作成 ≡ 集合から元を一つ取り出す
であり、
継承 ≡ 部分集合を定義
である。
Pipe ⊆ File (PipeはFileの部分集合である)なので、Pipeの元はいつもFileの元であり、PipeインスタンスはいつもFileポインタに代入可能である。
File f = new Pipe(/**/);
一方で、興味深い集合を定義するのはすごく難しい。
同じ問題を解決するにしても、それを解決するための一連の集合を定義するときには無数の選択肢がある。(「読み書きできるものはなんでもファイル」という素晴らしい抽象化の上でもこれは難しい問題だ。例えば↑のFileインターフェイスの各メソッドはどんなシグネチャにすべきだろうか?)面白い集合定義もあればつまらない集合定義もある。例えば数学で言うと群とか、結合法則と単位元と逆元の存在を仮定するだけというすごくシンプルなクラス設計でありながらものすごく色々な興味深い性質を持つ。しかしみんながみんなアーベルやガロアみたいに興味深いやり方で集合を定義できる、とは思えない。
うん。全然説明になってないな。どうみてもトートロジーです。本当にありがとうございました。
昔はオブジェクト指向大好きだったんだけど、PerlやJavaScriptのような関数をリテラルとして扱える言語を使うようになったら、この手の抽象化のほうがオブジェクト指向が取り扱う抽象化よりもよりシンプルでより強力なんじゃないかと思うようになった。
関数をリテラルとして扱うというのは、関数を関数の引数に渡したり、戻り値として返したり、変数に束縛したり、定数として用意したりすることだ。
数学モデルとしては、「関数を集合の元として扱うことを許す」みたいなことを意味する。そしてこれは数学的には自然というかよくやることだ。(例:実数係数の多項式全体からなる集合Fが自然な加算と定数倍についてベクトル空間となることを証明せよ)
#関係ないけどクロージャとかは数学的にはどういうものなんだろう。普通の写像とは違うような気がするけど。誰か偉い人教えてください。

