第2版の839ページで、Steve McConnellは、プログラマが大きなプログラムで「複雑さを克服する」ことができるすべての方法について説明しています。彼のヒントはこの声明で最高潮に達しています:
「オブジェクト指向プログラミングは、アルゴリズムとデータに同時に適用される抽象化のレベルを提供します、機能分解だけでは実現できなかった一種の抽象化提供します。」
「複雑さを減らすことが間違いなく効果的なプログラマーになるための最も重要な鍵である」(同じページ)という彼の結論と相まって、これは関数型プログラミングへのかなりの挑戦のようです。
FPとOOの間の論争は、FP課題に具体的に由来する複雑さの問題をめぐる支持者によってしばしばフレーミングされます並列性や並列化の問題ですが、ソフトウェアプログラマーが克服しなければならない複雑さは、同時実行性だけではありません。ある種の複雑さを軽減することに焦点を当てることで、他の次元では大幅に増加するため、多くの場合、利益に見合うだけの価値はありません。
FPとOO)の比較条件を同時実行性や再利用性などの特定の問題からグローバルな複雑さの管理に移した場合、その議論はどのように見えますか?
[〜#〜]編集[〜#〜]
私が強調したかったコントラストは、OOはデータとアルゴリズムの両方の複雑さからカプセル化して抽象化しているようですが、関数型プログラミングはデータ構造の実装の詳細を全体的に「公開」することを奨励しているようですプログラム。
たとえば、Stuart Halloway(Clojure FP proponent) here を参照して、「データ型の過剰指定」が「慣用的なOOスタイル "であり、AddressBookをより豊富なOOオブジェクトで、追加の(非ベクトル的で非マップ的)プロパティとメソッドではなく、単純なベクトルまたはマップとして概念化することを好む。(また、OOおよびドメイン駆動設計の支持者は、AddressBookをベクトルまたはマップとして公開すると、カプセル化されたデータが、ドメインの観点からは無関係または危険なメソッドに過度に公開されると言うことがあります)。
この本は20年以上前に書かれたものであることを覚えておいてください。その日のプロのプログラマーにとって、FPは存在しませんでした。それは完全に学者と研究者の領域にありました。
作業の適切なコンテキストで「機能分解」を組み立てる必要があります。著者は関数型プログラミングに言及していません。これを「構造化プログラミング」とそれに先行するGOTO
で満たされた混乱に関連付ける必要があります。参照ポイントが関数を持っていなかった古いFORTRAN/COBOL/BASIC(多分、運が良ければGOSUBの単一レベルが得られる)であり、すべての変数がグローバルであり、プログラムを分解できる場合機能の層に入れることは大きな恩恵です。
OOPは、この種の「機能分解」をさらに改良したものです。命令を関数にまとめるだけでなく、関連関数を、それらが作業しているデータとグループ化できます。その結果、明確に定義されたコード部分が得られ、コードベースを隅々まで追ってデータに作用する可能性のあるものを見つける必要なく、(理想的には)見て理解することができます。
関数型プログラミングの支持者は、ほとんどのFP言語は「関数型分解のみ」よりも多くの抽象化手段を提供し、実際にはオブジェクト指向言語のものに匹敵する抽象化手段を可能にすることを主張するでしょう。例として、Haskellの型クラスまたはMLの高次モジュールを抽象化の手段として引用することができます。したがって、ステートメント(関数型プログラミングではなく、オブジェクト指向と手続き型プログラミングに関するものであると確信しています)はそれらに適用されません。
FPとOOPは直交する概念であり、相互に排他的ではないため、これらを相互に比較しても意味がありません。 「命令型OOP」(Javaなど)と「関数型OOP」(Scalaなど)を非常によく比較できますが、引用したステートメントはその比較には適用されません。
関数型プログラミングは、複雑さの管理に非常に役立ちます。ただし、複雑さについては別の方法で考える傾向があり、OOPの意味でのカプセル化ではなく、さまざまなレベルで不変データに作用する関数として定義します。
たとえば、私は最近Clojureでゲームを作成し、ゲームの状態全体が単一の不変のデータ構造で定義されました。
(def starting-game-state {:map ....
:player ....
:weather ....
:other-stuff ....}
そして、メインゲームループは、ループ内のゲーム状態にいくつかの純粋な関数を適用することとして定義できます。
(loop [initial-state starting-game-state]
(let [user-input (get-user-input)
game-state (update-game initial-state user-input)]
(draw-screen game-state)
(if-not (game-ended? game-state) (recur game-state))))
呼び出されるキー関数はupdate-game
は、以前のゲームの状態とユーザー入力がある場合にシミュレーションステップを実行し、新しいゲームの状態を返します。
では、複雑さはどこにあるのでしょうか?私の見解では、それはかなりうまく管理されています。
OOPはカプセル化によって複雑さを管理することもできますが、これをOOPと比較すると、機能には非常に大きな利点があります。
最後に、機能対vs. OOP言語の複雑さを管理する方法についてのより多くの洞察に興味がある人のために、私は強くリッチヒッキーの基調講演のビデオを再認識しました Simple Made Easy =( 奇妙なループ テクノロジー会議で撮影)
「オブジェクト指向プログラミングは、アルゴリズムとデータに同時に適用されるレベルの抽象化を提供します。これは、機能分解だけでは提供できなかった一種の抽象化です。」
関数分解aloneは、あらゆる種類のアルゴリズムやプログラムを作成するには不十分です。データも表現する必要があります。上記のステートメントは、機能的な場合の「データ」が最も基本的な種類であると暗黙のうちに想定している(または少なくとも理解できる)と思います。シンボルのリストだけで、それ以外は何もありません。そのような言語でのプログラミングは明らかにあまり便利ではありません。ただし、Clojureなどの多くの、特に新しくモダンな関数型(またはマルチパラダイム)言語は、リストだけでなく、文字列、ベクトル、マップとセット、レコード、構造体、オブジェクトなどの豊富なデータ構造を提供します。 -メタデータとポリモーフィズム。
OO抽象化の大規模な成功は議論の余地はほとんどありません。しかし、それが最後の言葉ですか?あなたが書いたように、並行性の問題はすでに大きな問題であり、古典的なOOには同時実行性の考えはまったく含まれていません。その結果、事実上のOO同時実行性を処理するためのソリューションは、ダクトテープを重ねただけです:機能しますが、簡単に失敗します。手元にある重要なタスクから離れた脳リソースの量、そしてそれはうまくスケーリングしません多分多くの世界のベストを利用することが可能かもしれませんそれは現代のマルチパラダイム言語が追求しているものです.
可変状態は、プログラミングとソフトウェア/システム設計に関連する最も複雑で問題の原因です。
OOは変更可能な状態を受け入れます。 FP変更可能な状態を嫌います。
OOとFPには用途とスイートスポットがあります。賢明に選択してください。また、格言を覚えておいてください。「閉鎖は人間のオブジェクトではありません。オブジェクトは人間のクロージャではありません。 」
関数型プログラミングはオブジェクトを持つことができますが、それらのオブジェクトは不変である傾向があります。純粋な関数(副作用のない関数)は、それらのデータ構造で動作します。オブジェクト指向プログラミング言語で不変オブジェクトを作成することは可能ですが、それを行うように設計されておらず、それがオブジェクトの使用方法ではありません。これは、オブジェクト指向プログラムについて推論するのを難しくします。
非常に簡単な例を見てみましょう。 OracleがJava Stringsには逆のメソッドが必要であると判断し、次のコードを記述したとしましょう。
String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());
最後の行は何に評価されますか?これがfalseと評価されることを知るには、Stringクラスの特別な知識が必要です。
自分のクラスWuHoStringを作成した場合
String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())
最後の行が何に評価されるかを知ることは不可能です。
関数型プログラミングスタイルでは、次のように記述します。
String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))
そしてそれは本当でなければなりません。
最も基本的なクラスの1つで1つの関数を推論することが非常に難しい場合、可変オブジェクトのこのアイデアを導入することで複雑さが増加または減少したのではないかと疑問に思います。
明らかに、オブジェクト指向を構成するもの、それが機能的であることの意味、および両方を持つことの意味については、あらゆる種類の定義があります。私にとっては、ファーストクラスの関数のようなものを持たない言語で「関数型プログラミングスタイル」を持つことができますが、他の言語がそのために作られています。
ほとんどの場合、古典的なOOP抽象化は同時実行の複雑さをカバーしていません。したがって、OOP(元の意味で)はFPを除外していません。それはなぜscalaのようなものが表示されるのか。
答えは言語によって異なります。たとえば、Lispはコードを適切に整えていますis data--作成するアルゴリズムは実際には単なるLISPリストです!プログラムを作成するのと同じ方法でデータを保存します。この抽象化は、OOP)よりも簡単で完全なものであり、非常にきちんとしたことができます(マクロを確認してください)。
Haskell(と私が想像する同様の言語)は、まったく異なる答えを持っています:代数的データ型です。代数的データ型はC
構造体に似ていますが、より多くのオプションがあります。これらのデータ型は、データのモデル化に必要な抽象化を提供します。関数は、アルゴリズムのモデル化に必要な抽象化を提供します。型クラスとその他の高度な機能は、両方に対して抽象化のレベル高を提供します。
たとえば、楽しみのためにTPLと呼ばれるプログラミング言語に取り組んでいます。代数的データ型により、本当に値を簡単に表すことができます。
data TPLValue = Null
| Number Integer
| String String
| List [TPLValue]
| Function [TPLValue] TPLValue
-- There's more in the real code...
これは、非常に視覚的な方法で、TPLValue(私の言語の任意の値)がNull
またはNumber
であり、Integer
値または値のリスト(パラメーター)と最終値(本体)を含むFunction
ですらあります。
次に、型クラスを使用して、いくつかの一般的な動作をエンコードできます。たとえば、TPLValue
とShow
のインスタンスを作成すると、文字列に変換できるようになります。
さらに、特定の型(自分で実装しなかったものも含む)の動作を指定する必要がある場合は、独自の型クラスを使用できます。たとえば、Extractable
タイプのクラスを使用すると、TPLValue
を取り、適切な通常の値を返す関数を記述できます。したがって、extract
は、Number
をInteger
に、またはString
をString
に、Integer
およびString
はExtractable
のインスタンスです。
最後に、私のプログラムの主なロジックは、eval
やapply
などのいくつかの関数にあります。これらは実際にコアです。それらはTPLValue
sを受け取り、それらをより多くのTPLValue
sに変換し、状態とエラーを処理します。
全体として、私がHaskellコードで使用している抽象化は、実際にはmoreは、OOP言語で使用したものよりも強力です。
私が見る限り、引用された文はもはや有効ではありません。
現代OO言語は、種類が*でないタイプを抽象化することはできません。つまり、より高い種類のタイプは不明です。それらのタイプシステムでは、 "Int要素を含むコンテナ、要素に関数をマッピングします。」.
したがって、Haskellsのようなこの基本的な機能
fmap :: Functor f => (a -> b) -> f a -> f b
java *で簡単に作成することはできません。たとえば、少なくともタイプセーフな方法では作成できません。したがって、基本的な機能を取得するには、多くのボイラープレートを作成する必要があります。
それでも、これらの5つのメソッドは基本的に同じコードであり、いくつかを与えるか取ります。対照的に、Haskellでは、次のものが必要です。
これはJava 8で変更されないことに注意してください(関数をより簡単に適用できるというだけで、正確には、上記の問題が発生します。関数を注文すると、ほとんどの場合、より高い種類の型が何に適しているかを理解することさえできません。)
新しいOO Ceylonのような言語には、より高い種類のタイプはありません(最近、Gavin Kingに尋ねましたが、現時点では重要ではないと彼は私に言いました)。Kotlinについて知らない、しかし。
*)公平を期すために、メソッドfmapを持つインターフェースFunctorを持つことができます。悪いことは言えません:ねえ、私はライブラリクラスSuperConcurrentBlockedDoublyLinkedDequeHasMap、親愛なるコンパイラのfmapを実装する方法を知っています。