関数型プログラミングは、変装した命令型プログラミングではありませんか?


8

私が見ていたYouTubeビデオでは、命令型プログラミングと関数型プログラミングの違いについて、JavaとHaskellでそれぞれ1to からtoまでの数を10合計する方法を説明することで説明しました。

Javaでは、各ステップを明示的に記述し、各ステップの結果を変数に割り当てる必要があります-次のようなもの

int total = 0;
     for (int i = 1; i <= 10; i++){
         total = total + i;
     }
return total;

Haskellでは、次のように簡単に言うことができます。

sum(1..10)

私の質問は次のとおりです。関数型言語のバックグラウンドで何かが起こっていることは明らかであり、その何かはある種の命令型プロセスでなければなりません。関数型言語は、実際には一種の命令型言語のAPIのようです。たとえばsum(int start, int end)、Javaでメソッドを定義することにより、関数型言語の一部を作成できます。私は本当に新しいタイプの言語をすぐに作成しましたか、それとも命令命令をユーザーから隠す一連の命令メソッド呼び出しを定義しただけですか?

私が理解するのに苦労していることがはっきりしているといいのですが。


1
あなたが述べる理由のために、合計関数の例は良いものではありません。
David Richerby 2013

新しいタイプのコンピューターとは対照的に、それがスタイルと呼ばれる理由です。
user253751 2017年

回答:


10

MLやOCamlなどの関数型言語を使用するCやJavaなどの命令型言語のソースから実行中のコードにソースを変換するときに、前面の構文糖衣と背面のコード生成を切り離して何が起こるかを比較すると、通常は何を、なぜ、そしてどのように以下の違い

可変性と不変性

関数型プログラミングでは、不変値を使用する傾向があります。つまり、現在の関数の外部の手段によって値が変更されても心配する必要はありません。これを正しく使用すると、副作用に関連する問題がなくなります。

焦点:データと機能。

命令型でのコーディングについて考えるとき、最初にデータ構造を考え、次にそれらが必要とするメソッドを考えます。関数型プログラミングで作業するとき、最初に必要な関数を考え、次に必要なデータ型を作成します。ほとんどのデータ型は、遅延リスト(ストリームまたは無限リストと考える)または識別された共用体のいずれかです。判別された共用体が再帰的である場合、ウォーキングコードをすべて記述する必要なく、ツリーまたはグラフを即座に作成できます。

ジェネリック / パラメトリックポリモーフィズム

これは興味深いものです。私の事実が正しければ、関数型プログラミングで発明され、命令型プログラミングに移植されました。したがって、ジェネリックスが好きな場合は、関数型言語の設計者に感謝します。

参照の透明性 /並列化

参照透過性があるため、機能コードを並列計算に簡単に移植できます。

高次関数 /構成性

関数は新しい関数を作成して関数を返すことができるので、新しい関数を作成することは、新しいメソッド全体を記述する代わりに、新しい式を作成するのと同じくらい簡単です。これは、解く問題が数学で表現できる場合に非常に役立つを導きます。関数型プログラミングを使用すると、セット変換を実行し、SQLと更新を考えると、はるかに簡単になります。さまようロジックが 指摘関数型プログラミング言語が優れて場所です。

タイピング:静的対推論

型は書き込み中にプログラマーによって設定されるのではなく推論されるため、コードの正確さを確認するためにさらにチェックを行うことができ、多くの場合、関数はセット型ではなく汎用的になります。

パターンマッチングスイッチステートメント

マッチングを差別化されたユニオンと組み合わせる場合、マッチングはすべての結果をカバーしていることを確認するためにチェックされます。switchステートメントでケースを逃したため、ランタイムエラーが発生した回数。


「関数型プログラミングで作業するとき、最初に必要な関数を考え、次に必要なデータ型を作成します」:何をモデリングしているかによって異なります。特に再帰的なデータ型は、関数と同様にFPのホットスポットです。
Hibou57 14

「マッチングを差別化されたユニオンと組み合わせる場合、マッチングはすべての結果をカバーしていることを確認するためにチェックされます」:(補足として)ケースステートメントもチェックされ、Adaが必須です。必須であることは、パターンマッチングやカバレッジチェックを妨げるものではありません。
Hibou57 2014

@ Hibou57素敵なコメント。あなたの答えを見てみたいです。
ガイコーダ

Hibou57によって指摘されているように、関数の前にデータを最初に考えることができることに同意します。これを少し明確にしましょう。私は得意分野には関数型言語を、得意分野には命令型言語を、得意分野には論理言語を使用する傾向があり、2つ以上の言語を使用しても問題はありません。 1つの問題として、言語間でタスクを分離します。だから私は関数型言語を使用するとき、私は最初の関数を考える傾向があり、私は永続的なデータを必要とするとき、私は最初のデータを考える傾向があり、およびロジックは、私は論理型言語、などを使用
ガイコーダ

1
Adaのケースステートメントについては、ここで参照:5.4ケースステートメント(この日付までの最後のリビジョン)。特にそのことについては、「カバー」/「カバレッジ」などに関する文言に注意してください。
Hibou57 2014

9

関数型プログラミング言語は、それが禁止するもので注目に値します。既存の変数またはデータ構造の変更は禁止されています。一部の命令型プログラミング言語では「関数型」でプログラミングできますが、この言語は既存の変数またはデータ構造を誤って変更することから保護しません。たとえばsum、Javaの再帰的で機能的なバージョンは次のとおりです。

int sum(int start, int end) {
  if (start > end) return 0;
  else {
    int total = start + sum(start+1, end);
    return total;
  }
}

関数型プログラミング言語が優れているのは、それらすべてに「ファーストクラス」の機能があることです。つまり、整数やデータ構造のように、関数を値として使用できます。一部の命令型言語にもファーストクラスの関数(特にCおよびC ++)がありますが、Javaでは単一のメソッド(通常は呼び出されるapply()か何か)を持つオブジェクトで偽造する必要があります。ファーストクラスの関数を使用すると、sum関数を演算子を減らす関数:

interface IntegerOperator {
  int apply(int a, int b);
}

int sum(IntegerOperator op, int start, int end) {
  if (start > end) return 0;
  else {
    int total = op.apply(start, sum(op, start+1, end))
    return total;
  }
}

class AdditionOperator implements IntegerOperator {
  int apply(int a, int b) { return a+b; }
}

int total = sum(new AdditionOperator(), 1, 10);

関数型プログラミング言語で記述している場合、すべての関数は実際には関数(同じパラメーターで呼び出されるたびに同じ値を返す)であることを知っているので、次のことを知っています。

int total1 = sum(new ReallyStrangeOperator(), 1, 10);
int total2 = sum(new ReallyStrangeOperator(), 1, 10);

total1total2同じ値を持っている(たとえば、への2番目の呼び出しを最適化できますsum)が、命令型プログラミング言語ではReallyStrangeOperator(1,2)毎回同じ値を返すかどうかについてはわかりません。


2
本当に有益な答えをありがとう。私はもう1つを受け入れましたが、あなたのコメントも同じように簡単に受け入れ、このコメントをもう1つに書き込むことができます。
CodyBugstein 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.