関数型プログラミングは、「システムをモジュールに分解する際に使用される基準について」(データの隠蔽)から得られる利点を無視しますか?


27

システムをモジュールに分解する際に使用する基準について」という古典的な記事がありますが、これは初めて読んだばかりです。それは私にとって完全に理にかなっており、おそらくOOPのベースとなった記事の1つです。その結論:

これらの例により、フローチャートに基づいてシステムのモジュールへの分解を開始することはほとんど常に間違っていることを実証しようとしました。...各モジュールは、そのような決定を他のモジュールから隠すように設計されています

私の無学で経験の浅い意見では、関数型プログラミングはこの記事の正反対のアドバイスを取ります。私の理解では、関数型プログラミングはデータフローを慣用的にします。データは関数から関数に渡され、各関数はデータを密接に認識し、途中で「変更」します。そして、データの隠蔽が過大評価されているか、不必要であるか、または何かについて話しているリッチヒッキーのトークを見たことがあると思いますが、確かに思い出せません。

  1. まず、私の評価が正しいかどうか知りたいです。FPパラダイムとこの記事は哲学的に同意しませんか?
  2. 彼らが同意しないと仮定すると、FPはデータ隠蔽の欠如をどのように「補償」しますか?おそらく、データ隠蔽を犠牲にしてX、Y、およびZを獲得する可能性があります。X、Y、およびZがデータ隠蔽よりも有益である理由を知りたいのです。
  3. または、両者が同意しないと仮定すると、FPはデータの非表示が悪いと感じるかもしれません。もしそうなら、なぜデータ隠蔽が悪いと思うのですか?
  4. 彼らが同意すると仮定して、FPがデータ隠蔽の実装とは何かを知りたいです。OOPでこれを見るのは明らかです。privateクラス外の誰もアクセスできないフィールドを持つことができます。FPの場合、これについての明らかな類推はありません。
  5. 質問すべき他の質問もあると思いますが、質問すべきかはわかりません。それらにも自由に答えてください。

更新

このNeal Fordの講演には、非常に関連性の高いスライドが含まれています。ここにスクリーンショットを埋め込みます。

ここに画像の説明を入力してください


1
完全な質問に答えることはできませんが、(4)に関しては、カプセル化を提供できるいくつかのFP言語のモジュールシステムがあります。
アンドレスF。13年

@AndresF。ああそうだね。Haskellにはモジュールがあり、その中のデータ型と関数を非表示にできることを忘れていました。FPを言うとき、Clojureを本当に言っているのかもしれません。Clojureにはプライベート関数と「フィールド」を含めることができますが、データを何でも見えるようにしてどこにでも渡すのが慣用的だと思います。
ダニエルカプラン

6
よく行うことは、型を表示することですが、コンストラクターを非表示にすることです。これらの抽象タイプは、OCamlのモジュールシステムで特によく行われている
ダニエルGratzer

6
MLに似た言語では、コンストラクターにアクセスできないということは、その型の値をパターンマッチして分解することができないことを意味します。これらの値でできることは、使用可能な関数に渡すことだけです。これは、たとえばCで行われているのと同じ種類のデータ抽象化であり、パブリックまたはプライベートのいずれかのファーストクラスの概念はありません。
リュックダントン

1
@ SK-logic:「式の問題」の観点から、将来、新しい関数で拡張したい場合(およびデータを固定したままで問題ない場合)、データを非表示にすることは適切です。将来的に新しいデータ型で拡張するために(機能インターフェースを固定したままにすることで)
-hugomg

回答:


23

あなたが言及する記事は一般的なモジュール性に関するものであり、構造化された、機能的な、オブジェクト指向のプログラムにも同様に当てはまります。この記事は、OOPの大物の人から聞いたことがありますが、OOPに特化したものではなく、一般的なプログラミングに関する記事として読みました。関数型プログラミングについての有名な記事「なぜ関数型プログラミングが重要なのか」があり、結論の最初の文は「この論文では、モジュール性がプログラミングの成功の鍵であると主張しました」と述べています。(1)への答えはノーです。

適切に設計された関数は、必要以上にデータについて想定しているわけではないため、「データを十分に認識する」という部分は間違っています。(または、少なくともOOPの場合と同じくらい間違っています。高レベルの抽象化で厳密にプログラミングして、パラダイムのすべての詳細を永久に無視することはできません。最終的に、プログラムの一部は、データの特定の詳細。)

データの非表示はOOP固有の用語であり、記事で説明されている情報の非表示とはまったく同じではありません。この記事に隠されている情報は、設計が困難であるか変更される可能性が高い設計上の決定に関するものです。データ形式に関するすべての設計上の決定が難しいまたは変更される可能性が高いわけではなく、難しいまたは変更される可能性があるすべての決定がデータ形式に関するものではありません。個人的には、オブジェクト指向プログラマーがすべてをオブジェクトにしたい理由がわかりません。時には、単純なデータ構造だけで十分な場合があります。

編集:Rich Hickeyのインタビューから関連する引用を見つけました。

Fogus:その考えに従うと、Clojureがそのタイプでデータ隠蔽のカプセル化を行わないという事実に驚く人もいます。なぜデータ隠蔽をやめることにしたのですか?

ヒッキー:Clojureがプログラミングを抽象化に重点を置いていることを明確にしましょう。ただし、ある時点で、誰かがデータにアクセスする必要があります。また、「プライベート」という概念がある場合、それに対応する特権と信頼の概念が必要です。そして、それは非常に多くの複雑さと小さな価値を追加し、システムに硬直性をもたらし、しばしば物を本来あるべきでない場所に住まわせます。これは、単純な情報がクラスに入れられるときに発生する他の損失に追加されます。データが不変である限り、誰かが変更される可能性のあるものに依存するようになる可能性があることを除いて、アクセスを提供することによってもたらされる害はほとんどありません。まあ、大丈夫、人々は実際の生活の中で常にそうしていて、物事が変わると適応します。そして、それらが合理的であれば、彼らは、将来変化する可能性のある何かに基づいて決定を下すときを知っています。だから、それはリスク管理の決定であり、プログラマは自由にすべきだと思う。抽象化に向けてプログラミングを行い、実装の詳細と結婚することを警戒したいという感覚がなければ、優秀なプログラマーになることは決してありません。


2
オブジェクト指向プログラマーは、すべてをオブジェクトにしたくないのです。しかし、いくつかの事柄(たくさんの)は、カプセル化の恩恵を受ける。あなたの答えが実際に質問にどのように対処するかを理解するのに苦労しています。コンセプトはOOPに固有のものではなく、OOPには他の問題などがあると主張しているようです-ほんの数行の擬似コードであっても、明確な例を提供できますか?または、これを考慮に入れた設計のホワイトボードの説明ですか?または、ここでの主張を実証する何か?
アーロンノート

2
@Aaronaught:質問で提起された多くの(すべてではありませんが)ポイントに対処し、質問の論文と同様の方法でモジュール性を検討する関数型プログラミングに関する論文を参照しました。大部分は、この概念がOOPに固有のものではないという事実、彼の質問に対する答えです(質問を完全に誤解していない限り)。ここでは、OOPが他の問題を抱えていることについてはまったく話しませんでした。例の提供について良い点があります。私は良いものを思いつくことができるかどうかを確認します。
マイケルショー

2
「時には、単純なデータ構造だけで十分です」。+1 OOPが理にかなっている場合、FPの場合もあります。
ローランブルゴーロイ

1
@Aaronaughtこの答えは、FPの目標の1つであるモジュール化(カプセル化と再利用の両方)を示しているため(「なぜ機能プログラミングが重要なのか」で説明)、質問のポイント(1)に対する答えを「いいえ」。
アンドレス

2
@JimmyHoffaの情報隠蔽は、OO以外でも正気の原則です。haskellでは、ユーザーがデータ構造に関する最小限の知識で作業できるようにしたいのです。確かに、内部にアクセスできることは、何も変更できないため、それほど危険ではありません。ただし、1つのモジュール/データ構造/任意の抽象概念についてユーザーが見ないほど、リファクタリングの機会が増えます。Mapがバランスの取れたバイナリツリーであるか、コンピューターの小さなボックス内のマウスであるかは気にしません。これは、データの非表示の背後にある主な動機であり、オブジェクト指向以外でも有効です。
サイモンベルゴ

12

...そして、おそらくOOPのベースとなった記事の1つです。

そうではありませんが、議論に追加されました。特に、当時、彼が論文で説明した最初の基準を使用してシステムを分解するように訓練された開業医に追加されました。

まず、私の評価が正しいかどうか知りたいです。FPパラダイムとこの記事は哲学的に同意しませんか?

いいえ。また、私の目には、FPプログラムがどのように見えるかについてのあなたの説明は、プロシージャや関数を使用する他のプログラムと変わりません。

データは関数から関数に渡され、各関数はデータを密接に認識し、途中で「変更」します。

... 「親密性」の部分を除きます。親密性を正確に回避するために、抽象データに対して機能する関数を使用できる(そして実際に行うことが多いため)したがって、あなたはその「親密さ」をある程度制御でき、あなたが隠したいもののためのインターフェース(すなわち機能)をセットアップすることによって、あなたが好きなようにそれを調整することができます。

そのため、関数型プログラミングを使用して情報隠蔽のParnas基準をたどることができず、KWICインデックスの実装になり、彼の2番目の実装と同じような利点が得られない理由はありません。

彼らが同意すると仮定して、FPがデータ隠蔽の実装とは何かを知りたいです。OOPでこれを見るのは明らかです。クラス外の誰もアクセスできないプライベートフィールドを持つことができます。FPの場合、これとの明らかな類似性はありません。

データに関する限り、FPを使用してデータ抽象化とデータ型抽象化を作成できます。これらはいずれも、抽象として関数を使用して、具体的な構造とこれらの具体的な構造の操作を隠します。

編集

ここでは、FPのコンテキストでの「データの非表示」はそれほど役に立たない(またはOOPっぽい(?))と主張するアサーションが増えています。それで、SICPから非常にシンプルで明確な例をここにスタンプさせてください:

システムが有理数で動作する必要があるとします。それらを表現する1つの方法は、分子または分母という2つの整数のペアまたはリストとしてです。したがって:

(define my-rat (cons 1 2)) ; here is my 1/2 

あなたはデータの抽象化を無視した場合、最も可能性の高いあなたが使用して分子と分母を取得しますcarcdr

(... (car my-rat)) ; do something with the numerator

このアプローチに続き、有理数を操作するシステムのすべての部分は、合理的な数であることを知っているだろうcons-彼らがしますcons有理数を作成し、リスト演算子を使用してそれらを抽出するための番号。

直面する可能性のある問題の1つは、有理数の形式を縮小する必要がある場合です。システム全体で変更が必要になります。また、作成時に削減することにした場合、合理的な用語の1つにアクセスするときに削減する方が良い場合があります。

別の問題は、仮にそれらの代替表現が好まれ、そのcons表現を放棄することに決めた場合です-再びフルスケールの変更。

これらの状況に対処するための適切な努力は、おそらく、インターフェースの背後にある合理性の表現を隠し始めるでしょう。最後に、次のような結果になる可能性があります。

  • (make-rat <n> <d>)分子が整数で分母が整数<n>である有理数を返します<d>

  • (numer <x>)有理数の分子を返します<x>

  • (denom <x>)有理数の分母を返します<x>

そして、システムは、どの理性が作られているかをもはや知りません(そして、もはやすべきではありません)。これがためであるconscarcdr有理数に固有のものではなくmake-ratnumerdenom しています。もちろん、これは簡単にFPシステムにすることができます。したがって、「データの隠蔽」(この場合、データ抽象化、または表現と具体的な構造をカプセル化する努力として知られています)は、オブジェクト指向、関数型プログラミング、またはなんでも。

そして、ポイントは...彼らがしている「隠しの種類」またはカプセル化(手順の抽象化の場合、設計決定、データ構造またはアルゴリズムを隠しているかどうか)を区別しようとするかもしれませんが、それらはすべて同じテーマを持っています。それらは、パルナスが明確にした1つ以上のポイントによって動機付けられています。あれは:

  • 変更可能性:必要な変更をローカルで行うことができるか、システム全体に分散するか。
  • 独立した開発:システムの2つの部分を並行して開発できる程度。
  • わかりやすさ:システムのどの部分がその部分の1つを理解するために知られている必要があるか。

上記の例はSICPの本から引用したものなので、本でのこの概念の完全な議論とプレゼンテーションのために、第2章をチェックすることを強くお勧めします。また、FPのコンテキストで抽象データ型に精通することをお勧めします。これは、テーブルに他の問題をもたらします。


データの非表示がFPに関連していることに同意します。そして、あなたが言うように、これを達成する方法があります。
アンドレス

2
あなたはちょうど私のポイントを美しくしました:あなたはデータを隠さないこれらの関数を持っています、それらはデータを取得する方法を説明する式であり、したがって、非表示を心配する必要のないデータフィールドではなく式に抽象化を持つプライベートメンバーを使用して複雑なオブジェクトを作成するか、cons値にアクセスできないようにすることでデータを生成します。生成、取得、合理的データとのやり取りのアクティビティが表現されます。したがって、データを変更しても式を変更します。
ジミー・ホッファ

8

関数型プログラミングにはデータ隠蔽がないというあなたの信念は間違っています。データを非表示にする別のアプローチが必要です。関数型プログラミングでデータを非表示にする最も一般的な方法の1つは、関数を引数としてとるポリモーフィック関数を使用することです。たとえば、この関数

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

データの最も外側の構造(つまり、リスト)のみが表示され、リストに含まれるデータについては何も表示されず、渡される単一の関数を介してのみデータを操作できます。

引数として渡される関数は、リストに含まれるデータ型のパブリックメソッドに似ています。データの操作方法は限られていますが、データ型の内部動作を公開しません。


5

私はここで四肢を攻撃し、FPの概念はオブジェクト指向のようには関係がないと言います。

tl; dr; データ隠蔽のポイントは、責任があるべき場所で維持されることを保証することであり、知識のないデータをいじる外部のアクターがいません。FPでは、データは式によって生成されます。この方法では、ゲームのルールを完全に変更する合成可能な計算ほど可変プロパティではないため、データを混乱させることはできません。


FPに関する私の経験では、明らかに重要ではありませんが、良い/一般的なデータモデリングを示すものにおいて、OOとはまったく対照的なものを見つける傾向があります。

この対照は、オブジェクト指向では一般的に、データを表現するために物事をモデル化することです。義務的な車の例え:

OO

  • ACオブジェクトの実装など、車に関する詳細を正しく非表示にする車オブジェクトがあります(ベルト駆動ですか、それとも空気圧駆動ですか?消費者は知る必要がないので、非表示にします)。
  • この自動車オブジェクトには、自動車に関するすべての事実と自動車での作業方法を記述する多くのプロパティとメソッドがあります。
  • このcarオブジェクトには、carの特定の実装とcarのコンポーネントを交換可能にするデータファクト全体からさらに隠すcarのコンポーネントであるプロパティがあります。

ここで注意すべきことは、オブジェクトをオブジェクト指向形式でモデリングしているとき、すべてをデータとして表現することです。プロパティを持つオブジェクトがあり、それらのプロパティの多くはより多くのプロパティを持つオブジェクトです。これらのオブジェクトにはあちこちにいくつかのメソッドがアタッチされていますが、それらが実際に行うのは、通常、このようにオブジェクトのプロパティをジガーすることだけです。これも非常にデータ中心のモデリングです。つまり、消費者がデータをこのように変更できるように、データのすべてのポイントを利用できるように構造化することに焦点を当てて、対話するデータをモデル化します。

FP

  • あなたは行動を記述することを可能にする多くの計算を持っています
  • これらの行動の表現は、自動車の行動が互いに関連する方法に変換できる方法で関連付けられています。たとえば、加速/減速を行う自動車など、同様の方法で互いに反対する2つの行動があります。

OOとFPの大きな違いは、常にデータをモデル化する方法について前述したとおりです。前述のOOでは、データとしてデータをモデル化しますが、FPでは、データを計算、式、アルゴリズムとしてモデル化します。それは、データの事実よりもデータのアクティビティをモデル化することです。数学での基本的なデータモデリングについて考えてください。オブジェクト指向ではモデリングを使用してデータを表現する方法とは対照的に、常にデータを生成できる方程式を取得することが重要です。これは、FPとOOの違いの多くです。

基本的なFP言語の1つであるLISPが、非常に少量のプリミティブデータ型で生活していたことを覚えておいてください。これは、アプローチがシステムの動作を生成および表現する計算ほどデータの複雑な表現をモデル化することではないため、機能します。

FPでコードを書き始めるときは、何かを行うコードを書くことから始めます。OOでコードを書き始めるときは、何かを記述するモデルを書くことから始めます。物事の実行は式によってFPに隠され、物事の実行はデータで記述されることでオブジェクト指向に公開され、このデータを非表示にすることでその公開が制限されます。


手元の質問に戻りますが、FPはデータの隠蔽について何と言っていますか?

OOでは、あなたのデータはあなたのプログラムの内なる重要な部分であり、邪魔されないようにする必要があります。FPでは、システムの根性と知識はすべて、システムを表現するアルゴリズムと計算に隠されています。これらは定義上多かれ少なかれ不変であり、計算式を変更する唯一の方法はマクロのようなものですが、それでもあなたは突然変異の定義はそれ以上調整することができない式そのものです。


これは素晴らしいです、私はそれを読んで本当に楽しかったです。ご協力いただきありがとうございます
クリスマッコール

5

ここには少し矛盾があります。関数型プログラミングは、機能に焦点を合わせているだけでなく、プリミティブデータ型に直接作用する関数を頻繁に備えていますが、オブジェクト指向プログラミングよりも多くのデータが隠れている傾向があります。

これはどうですか?基礎となるデータ、おそらくコレクションを隠す素敵なオブジェクト指向インターフェースについて考えてみてください(私はほぼどこにでもあるものを選んでいます)IEnumerableなどのコレクションが実装していることがわかっている限り、コレクション内のオブジェクトの基本型やコレクションを実装しているオブジェクトの型を知る必要はないかもしれません。したがって、データが非表示になります。

関数型プログラミングでは、IEnumerableインターフェイスで効果的に機能するが、プリミティブデータ型(または任意のデータ型)で動作する関数を作成できます。しかし、型がIEnumerableメソッドを実装しなかった場合はどうでしょうか?ここにキーがあります。「インターフェイス」の必要な部分を形成する「メソッド」を、常に関数に渡されるパラメーターにすることができます。 または、関数をデータと組み合わせて、オブジェクト指向のような方法で物事を行うことができます。

どちらの方法でも、オブジェクト指向の場合よりもデータの非表示が少ないことに注意してください。任意の型で動作する私の一般的な関数は明らかにその型のデータにアクセスしていません-これは一般的な関数にパラメーターとして渡された関数内で発生しますが、一般的な関数はデータを見るためにそれらの関数の内部を覗くことはありません。

したがって、あなたのポイント1に関しては、FPと記事が実際に同意しないと思います。FPがデータを非表示にしないという特性評価は正しいとは思いません。FPで著者が好んだデザインを実装することは確かです。

ポイント4(2と3は、ポイント1について私が言ったことを考えると、答える意味がありません)については異なります。また、オブジェクト指向言語でも異なり、多くのプライベートフィールドでは、言語によって強制されるのではなく、慣例によりプライベートになっています。


言い換えると、関数型プログラミングでは、単に存在しないというだけの理由で、デフォルトでははるかに多くが「隠されています」!明示的にスコープ内に持ち込むもののみが「隠されていない」。
左回り14

3

まず、この素晴らしい記事へのリンクのおかげで、私はこれまでこれを知りませんでした。そして、ここ数年でコミュニティの他のソフトウェアデザイナーと話し合っていたいくつかのことについて素晴らしいインプットをくれました。これについての私の意見は次のとおりです。

まず、私の評価が正しいかどうか知りたいです。FPパラダイムとこの記事は哲学的に同意しませんか?

FPデザインは、データフローに非常に焦点を当てています(これは、記事で暗示されているほど悪くはありません)。これが完全な「意見の相違」である場合は議論の余地があります。

彼らが同意しないと仮定すると、FPはデータ隠蔽の欠如をどのように「補償」しますか?おそらく、データ隠蔽を犠牲にしてX、Y、およびZを獲得する可能性があります。X、Y、およびZがデータ隠蔽よりも有益である理由を知りたいのです。

私見それは補償しません。下記参照。

または、両者が同意しないと仮定すると、FPはデータの非表示が悪いと感じるかもしれません。もしそうなら、なぜデータ隠蔽が悪いと思うのですか?

FPユーザーやデザイナーのほとんどは、このように感じたり考えたりすることはないと思います。以下を参照してください。

彼らが同意すると仮定して、FPがデータ隠蔽の実装とは何かを知りたいです。OOPでこれを見るのは明らかです。クラス外の誰もアクセスできないプライベートフィールドを持つことができます。FPの場合、これとの明らかな類似性はありません。

ここにポイントがあります-あなたはおそらくあなたがOOPが非機能的であると信じるほど多くのOOPシステムが非機能的な方法で実装されているのを見ました。それは誤りです。IMHOOOPとFPはほとんど直交する概念であり、機能的なオブジェクト指向システムを完全に構築することができます。これにより、質問に対する明らかな答えが得られます。FPでの古典的な「オブジェクト」実装は、クロージャを利用することで行われます。機能システムでオブジェクトを使用したい場合、キーポイントはそれらを不変に設計することです。

したがって、より大きなシステムを作成するために、私見では、オブジェクト指向の概念を使用して、モジュールのクラス、オブジェクトを作成できます。お気に入りのFP言語のモジュールコンセプトを使用し、すべてのオブジェクトを不変にし、「両方の世界のベスト」を使用します。


3

TL; DR:いいえ

FPパラダイムとこの記事は哲学的に同意しませんか?

いいえ、そうではありません。関数型プログラミングは宣言的であり、「制御フローを記述せずに計算のロジックを表現するコンピュータープログラムの構造と要素を構築するスタイル」です。 フローチャートに従うのではなく、フローを独自に発生させるルールを作成するようなものです。

手続き型プログラミングは、関数型プログラミングよりもフローチャートのエンコードにはるかに近いです。したがって、発生する変換、およびそれらの変換のエンコードは、フローチャートのフローで説明されているとおりに、順番に実行されるプロシージャにエンコードされます。

手続き型言語は、共有状態を暗黙的に変更する可能性のある一連の命令型コマンドとしてプログラムの実行をモデル化しますが、関数型プログラミング言語は、引数と戻り値の点でのみ互いに​​依存する複雑な式の評価として実行をモデル化します。このため、機能プログラムはより自由なコード実行順序を持つことができ、言語はプログラムのさまざまな部分が実行される順序をほとんど制御できない可能性があります。(たとえば、Schemeのプロシージャ呼び出しの引数は、任意の順序で実行されます。)

データ隠蔽

  • 関数型プログラミングには、たとえばクロージャーを考えるなど、データを隠す独自の方法があります。それは、クロージャーでのカプセル化によるデータの隠蔽です。クロージャーのみがデータへの参照を持ち、クロージャーの外部では参照できないため、フィールドがクローズされたプライベートデータになることは困難です。
  • データが非表示になる理由の1つは、変化するデータを非表示にしてプログラミングインターフェイスを安定させることです。関数型プログラミングではデータを変更する必要がないため、データを非表示にする必要はありません。

3
「関数型プログラミングにはデータを変更する機能がないため、それほど多くのデータを隠す必要はありません。」-これは非常に誤解を招く主張です。行動をカプセル化する理由の1つは、データの突然変異を制御することだと自分自身(そして私も同意します)を言いました。しかし、突然変異の欠如がカプセル化をほとんど役に立たないものにすると結論付けることは非常に大きなストレッチです。一般に、ADTとデータの抽象化は、FPの文献とシステムに広まっています。
チアゴシルバ

「カプセル化はほとんど役に立たない」とは言わなかった。これらはあなたの考えであり、あなただけのものです。変化する変数がないため、それほど多くのデータを隠す必要はないと言いました。これにより、カプセル化やデータ隠蔽が役に立たなくなりますが、これらのケースが存在しないため、使用量が減ります。データの隠蔽とカプセル化が有用な他のすべてのケースは依然として有効です。
-Dietbuddha
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.