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が自然な加算と定数倍についてベクトル空間となることを証明せよ)
#関係ないけどクロージャとかは数学的にはどういうものなんだろう。普通の写像とは違うような気がするけど。誰か偉い人教えてください。
この記事にコメントする
・・・
オブジェクト指向を理解しないまま関数型言語を知ってしまったようですね。
確かに関数型言語の抽象化レベルはオブジェクト指向の抽象化レベルを遥かに超えていますが、だからといってそれが絶対的な正解ではありません。
一番の問題は、オブジェクト指向よりも嫌われる要素が多いからです。世界は、あなたのような人ばかりがいるわけではないのです。
あと、あなたのオブジェクト指向の説明は、数学モデルによくマッチしている関数型言語には適用できますが、それを無理にオブジェクト指向に適用しているために説明し切れていないのだと思います。
確かに関数型言語の抽象化レベルはオブジェクト指向の抽象化レベルを遥かに超えていますが、だからといってそれが絶対的な正解ではありません。
一番の問題は、オブジェクト指向よりも嫌われる要素が多いからです。世界は、あなたのような人ばかりがいるわけではないのです。
あと、あなたのオブジェクト指向の説明は、数学モデルによくマッチしている関数型言語には適用できますが、それを無理にオブジェクト指向に適用しているために説明し切れていないのだと思います。
無題
>世界は、あなたのような人ばかりがいるわけではないのです。
オブジェクト指向も理解できないほど馬鹿な僕にも、(少なくともメリットや美しさを少しは感じられる程度には)関数型言語が理解できるんだとしたら、オブジェクト指向より関数型を理解する方が易しいと言えませんか?ま、向き不向きはあるでしょうから一般化できる話ではないですけど。
説明のしかたが悪いのかもしれないのですが、「クラス設計って本質的に難しいんじゃない?」というのが僕の問いというか仮説です。
この仮説が間違ってるとすると、「クラス設計は本質的に易しい」ことになるかと思いますが、どうなんでしょうか。
こちらで言及した
http://blog.ajiyoshi.org/Entry/264/
id:JavaBlackさんという方の観測によれば、
http://d.hatena.ne.jp/JavaBlack/searchdiary?word=%2a%5bOOP%5d
オブジェクト指向を正しく理解してない人が*すごく*いっぱいいるみたいで(僕もその一人なのでしょう)、しかも正しく理解しないままだと簡単にデスマーチに陥るそうです。
そんなに多くの人が勘違いいていて、しかも勘違いしていると多大なデメリットがあるような問題解決手法が本質的に易しいとは、あまり思えないのですが・・
オブジェクト指向も理解できないほど馬鹿な僕にも、(少なくともメリットや美しさを少しは感じられる程度には)関数型言語が理解できるんだとしたら、オブジェクト指向より関数型を理解する方が易しいと言えませんか?ま、向き不向きはあるでしょうから一般化できる話ではないですけど。
説明のしかたが悪いのかもしれないのですが、「クラス設計って本質的に難しいんじゃない?」というのが僕の問いというか仮説です。
この仮説が間違ってるとすると、「クラス設計は本質的に易しい」ことになるかと思いますが、どうなんでしょうか。
こちらで言及した
http://blog.ajiyoshi.org/Entry/264/
id:JavaBlackさんという方の観測によれば、
http://d.hatena.ne.jp/JavaBlack/searchdiary?word=%2a%5bOOP%5d
オブジェクト指向を正しく理解してない人が*すごく*いっぱいいるみたいで(僕もその一人なのでしょう)、しかも正しく理解しないままだと簡単にデスマーチに陥るそうです。
そんなに多くの人が勘違いいていて、しかも勘違いしていると多大なデメリットがあるような問題解決手法が本質的に易しいとは、あまり思えないのですが・・
本質的に簡単なものなんて・・・
本質的に簡単なものなんて存在しないんじゃない?というのが私の持論です。
また、簡単に美しさが感じられるからといって、その先が簡単であるということではありません。
私の周りをみると、
オブジェクト指向:勘違いしているとしても使える(手続き型の拡張として)
関数型:全く使えない
ということが多いです。さて、例としては少ないですが、この状況はどう考えますか?
また、簡単に美しさが感じられるからといって、その先が簡単であるということではありません。
私の周りをみると、
オブジェクト指向:勘違いしているとしても使える(手続き型の拡張として)
関数型:全く使えない
ということが多いです。さて、例としては少ないですが、この状況はどう考えますか?
無題
>本質的に簡単なものなんて存在しないんじゃない?というのが私の持論です。
特に異論はないですけど、通りすがりさんも「難しさに順序がある」ことを暗に仮定されてますよね。(この後の問いかけは「関数型を理解するよりオブジェクト指向を理解する方が簡単な可能性がある」とおっしゃりたいのですよね?)
足し算より掛け算の方が難しいだろうし、掛け算より一次関数をあれこれするほうが難しいだろうし、一次関数より二次関数のほうが難しいだろうと思います。
>ということが多いです。さて、例としては少ないですが、この状況はどう考えますか?
単に今まではメジャーな言語で関数的機能を備えたものが少なかっただけじゃないでしょうか?関数型言語そのものの難しさに起因するとは限らないと思います。(Lispのソースを見てドン引きする人がいるのは関数型言語であることが主な理由じゃないと思います)。
それなりに人気のある言語で、例えばレキシカルクロージャを備えた言語ってPerlとJavaScriptくらいだと思います(あとはC#3.0とかJDK7とか?まだ使ってる人いませんね)。Perlは自前で高階関数を書かないとしても、map/grep/sortなんかでそれと知らないうちにレキシカルクロージャの恩恵に預かってるだろうし、JavaScriptは近代的で強力な言語だと認知されたのがほんのここ1,2年のことですよね。
というか、オブジェクトを引数として渡したり返り値として返したりリテラル表記(Javaの無名クラスとか)することを許容できるなら、関数やクロージャを引数として渡したり返り値として返したりリテラル表記することを許容できないとは思えないです。
特に異論はないですけど、通りすがりさんも「難しさに順序がある」ことを暗に仮定されてますよね。(この後の問いかけは「関数型を理解するよりオブジェクト指向を理解する方が簡単な可能性がある」とおっしゃりたいのですよね?)
足し算より掛け算の方が難しいだろうし、掛け算より一次関数をあれこれするほうが難しいだろうし、一次関数より二次関数のほうが難しいだろうと思います。
>ということが多いです。さて、例としては少ないですが、この状況はどう考えますか?
単に今まではメジャーな言語で関数的機能を備えたものが少なかっただけじゃないでしょうか?関数型言語そのものの難しさに起因するとは限らないと思います。(Lispのソースを見てドン引きする人がいるのは関数型言語であることが主な理由じゃないと思います)。
それなりに人気のある言語で、例えばレキシカルクロージャを備えた言語ってPerlとJavaScriptくらいだと思います(あとはC#3.0とかJDK7とか?まだ使ってる人いませんね)。Perlは自前で高階関数を書かないとしても、map/grep/sortなんかでそれと知らないうちにレキシカルクロージャの恩恵に預かってるだろうし、JavaScriptは近代的で強力な言語だと認知されたのがほんのここ1,2年のことですよね。
というか、オブジェクトを引数として渡したり返り値として返したりリテラル表記(Javaの無名クラスとか)することを許容できるなら、関数やクロージャを引数として渡したり返り値として返したりリテラル表記することを許容できないとは思えないです。
無題
ああ、そうだ。
http://web.yl.is.s.u-tokyo.ac.jp/~ganat/memo/aboutHaskell.html
SQLも関数型言語と言えるみたいですよ。
SQL使える方は周りにいらっしゃらないですか?
http://web.yl.is.s.u-tokyo.ac.jp/~ganat/memo/aboutHaskell.html
SQLも関数型言語と言えるみたいですよ。
SQL使える方は周りにいらっしゃらないですか?
無題
難しさに順序があると言いたかったのではなく、要は、銀の弾丸なんて存在しない、ということが言いたかったのですが・・・
関数型言語がもしオブジェクト指向よりも本質的に簡単だったとしても、銀の弾丸にはなりえない理由がいくつも存在する、ということです。
SQLをSQLとして使える人というのはかなり少数派です。
SQLが使える≠SQLを集合指向言語として使える
また、Javaプログラマであっても無名クラスが許容できない人も多く存在します。
>単に今まではメジャーな言語で関数的機能を備えたものが少なかっただけじゃないでしょうか?
しかし、現在普及してしまったのは手続きを基本とした言語です。手続き的な思考に慣れてしまった人が関数型の思考に切り替えるのは、なかなか難しいのです。
>この後の問いかけは「関数型を理解するよりオブジェクト指向を理解する方が簡単な可能性がある」とおっしゃりたいのですよね?
上でも述べましたが、違います。オブジェクト指向言語は正しく理解していなくても、手続き型言語の延長として使えますが、関数型言語はそう簡単にはいかない、ということです。もう一度言いますが、関数型言語といえど、銀の弾丸にはなりえない。
関数型言語がもしオブジェクト指向よりも本質的に簡単だったとしても、銀の弾丸にはなりえない理由がいくつも存在する、ということです。
SQLをSQLとして使える人というのはかなり少数派です。
SQLが使える≠SQLを集合指向言語として使える
また、Javaプログラマであっても無名クラスが許容できない人も多く存在します。
>単に今まではメジャーな言語で関数的機能を備えたものが少なかっただけじゃないでしょうか?
しかし、現在普及してしまったのは手続きを基本とした言語です。手続き的な思考に慣れてしまった人が関数型の思考に切り替えるのは、なかなか難しいのです。
>この後の問いかけは「関数型を理解するよりオブジェクト指向を理解する方が簡単な可能性がある」とおっしゃりたいのですよね?
上でも述べましたが、違います。オブジェクト指向言語は正しく理解していなくても、手続き型言語の延長として使えますが、関数型言語はそう簡単にはいかない、ということです。もう一度言いますが、関数型言語といえど、銀の弾丸にはなりえない。
無題
人月の神話でブルックスが「オブジェクト指向が銀の弾丸になるといいな。でもダメだろうな」的なことを書いていたように記憶しています。
僕も関数型言語がソフトウェア開発の困難さの90%を解消して10倍の生産性が得られるようになる、とは思いません。
でも、銀の弾丸がないことは、よりよい手段を追求しても無意味であることを意味しないと僕は考えます。100年後にやっぱり銀の弾丸はなかったね、と言われるんだとしても。
普通のやつらの上を行け的な意味で、より強力な手法があって、それを選択することができるなら、やっぱりそれは使うべきだと思います。
僕も関数型言語がソフトウェア開発の困難さの90%を解消して10倍の生産性が得られるようになる、とは思いません。
でも、銀の弾丸がないことは、よりよい手段を追求しても無意味であることを意味しないと僕は考えます。100年後にやっぱり銀の弾丸はなかったね、と言われるんだとしても。
普通のやつらの上を行け的な意味で、より強力な手法があって、それを選択することができるなら、やっぱりそれは使うべきだと思います。
無題
トラックバックが飛ばないようなのでコメント。
「それは間違ってる」みたいなことを強い口調で言われると「そうなのかな?」と思って自信をなくしてしまうのだけれど、某Javaナントカさんは「間違ってる」と言うだけで「どこがどう間違ってるか」を言わないので無視することにしました。
「それは間違ってる」みたいなことを強い口調で言われると「そうなのかな?」と思って自信をなくしてしまうのだけれど、某Javaナントカさんは「間違ってる」と言うだけで「どこがどう間違ってるか」を言わないので無視することにしました。
今更コメントするのも気が引けますが
難しいのは「抽象化」です。設計とは抽象と具象のハザマで方針を示す事です。
抽象化の程度と方向性で色々な事を言われますが、私の感触としては、設計をきちんと出来る技術者が少ないのです。オブジェクト指向とか構造化とか関係ありません。
設計ではなく実装レベルで言うなら、慣れた方法とは違うやり方に抵抗があるのは当たり前です。特に技術者とか研究者とかはその時の感情が顕著に現れます(それも理由があるのですが話の方向が明後日に向かい始めるので省略)
そしてプログラムとは手続きを記述すること。手続き型言語が世に広く受け入れられているのもその為と私は考えています。そこから大規模開発の為の方法論として構造化→オブジェクト指向と進んできた訳ですが、歴史の順序が示す通り、オブジェクト指向は最後に出会う方法論です。ベタな手続きからの抽象度が高いので難しいのです。
なお、関数型言語は、言語研究の分野では極めてメジャーだと思いますよ。歴史的にも極めて古くから存在します。開発現場で使える技術者が少ない為にマイナーに見えるのでしょう。関数型言語で研究されてこなれてから手続き型言語に取り入れられた機能は多いです。
抽象化の程度と方向性で色々な事を言われますが、私の感触としては、設計をきちんと出来る技術者が少ないのです。オブジェクト指向とか構造化とか関係ありません。
設計ではなく実装レベルで言うなら、慣れた方法とは違うやり方に抵抗があるのは当たり前です。特に技術者とか研究者とかはその時の感情が顕著に現れます(それも理由があるのですが話の方向が明後日に向かい始めるので省略)
そしてプログラムとは手続きを記述すること。手続き型言語が世に広く受け入れられているのもその為と私は考えています。そこから大規模開発の為の方法論として構造化→オブジェクト指向と進んできた訳ですが、歴史の順序が示す通り、オブジェクト指向は最後に出会う方法論です。ベタな手続きからの抽象度が高いので難しいのです。
なお、関数型言語は、言語研究の分野では極めてメジャーだと思いますよ。歴史的にも極めて古くから存在します。開発現場で使える技術者が少ない為にマイナーに見えるのでしょう。関数型言語で研究されてこなれてから手続き型言語に取り入れられた機能は多いです。

