116

手続き型プログラミングと関数型プログラミングのパラダイムの違いを理解するのに本当に苦労しています。

関数型プログラミングに関するウィキペディアのエントリの最初の2つの段落は次のとおりです。

コンピュータサイエンスでは、関数型プログラミングは、計算を数学関数の評価として扱い、状態データや可変データを回避するプログラミングパラダイムです。状態の変化を強調する命令型プログラミングスタイルとは対照的に、関数の適用を強調します。関数型プログラミングのルーツはラムダ計算です。ラムダ計算は、関数の定義、関数の適用、および再帰を調査するために1930年代に開発された形式的なシステムです。多くの関数型プログラミング言語は、ラムダ計算の詳細と見なすことができます。

実際には、数学関数と命令型プログラミングで使用される「関数」の概念との違いは、命令型関数には副作用があり、プログラム状態の値が変わる可能性があることです。このため、参照透過性が不足しています。つまり、同じ言語式では、実行中のプログラムの状態に応じて、異なる時間に異なる値が生成される可能性があります。逆に、関数型コードでは、関数の出力値は関数に入力された引数のみに依存する fため、引数に同じ値を指定して関数を2回呼び出すとx、同じ結果が生成されます。f(x)両方の時間。副作用を排除することで、関数型プログラミングの開発の主な動機の1つである、プログラムの動作の理解と予測がはるかに容易になります。

それが言うパラグラフ2で

逆に、関数型コードでは、関数の出力値は関数に入力された引数のみに依存するfため、引数に同じ値を指定して関数を2回呼び出すと、両方の時間xで同じ結果が生成されます。f(x)

手続き型プログラミングの場合とまったく同じではありませんか?

目立つ手続き型と機能型で何を探すべきですか?

4

9 に答える 9

281

関数型プログラミング

関数型プログラミングとは、関数を値として扱う能力を指します。

「通常の」値との類推を考えてみましょう。2 つの整数値+を取得し、演算子を使用してそれらを結合して、新しい整数を取得できます。または、整数に浮動小数点数を掛けて浮動小数点数を取得できます。

関数型プログラミングでは、 composeliftなどの演算子を使用して、2 つの関数値を組み合わせて新しい関数値を生成できます。または、 mapfoldなどの演算子を使用して、関数値とデータ値を組み合わせて新しいデータ値を生成できます。

多くの言語には関数型プログラミング機能があることに注意してください。通常は関数型言語とは考えられていない言語も含まれます。祖父の FORTRAN でさえ関数値をサポートしていましたが、関数を結合する演算子についてはあまり提供していませんでした。言語が「関数型」と呼ばれるには、関数型プログラミング機能を大々的に取り入れる必要があります。

手続き型プログラミング

手続き型プログラミングとは、共通の命令シーケンスをプロシージャにカプセル化して、コピー アンド ペーストに頼ることなく多くの場所からこれらの命令を呼び出すことができる機能を指します。プロシージャーはプログラミングの非常に初期の開発であったため、この機能はほぼ常に、機械語またはアセンブリー言語のプログラミングで要求されるプログラミングのスタイルと関連付けられています。これは、記憶域の場所とそれらの場所間でデータを移動する命令の概念を強調するスタイルです。

対比

この 2 つのスタイルは正反対というわけではありません。互いに異なるだけです。両方のスタイルを完全に取り入れている言語があります (LISP など)。次のシナリオは、2 つのスタイルの違いを感じさせるかもしれません。リスト内のすべての単語の文字数が奇数であるかどうかを判断するナンセンス要件のコードを書きましょう。まず、手続き型のスタイル:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

この例はわかりやすいと思います。さて、機能的なスタイル:

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

内側から作業すると、この定義は次のことを行います。

  1. compose(odd, length)oddと関数を組み合わせてlength、文字列の長さが奇数かどうかを判断する新しい関数を生成します。
  2. map(..., words)の各要素に対してその新しい関数を呼び出しwords、最終的にブール値の新しいリストを返します。各リストは、対応する単語の文字数が奇数かどうかを示します。
  3. apply(and, ...)結果のリストに「 and」演算子を適用し、すべてのブール値をまとめて -ing して最終結果を生成します。

これらの例からわかるように、手続き型プログラミングは、変数内で値を移動し、最終結果を生成するために必要な操作を明示的に記述することに非常に関心があります。対照的に、機能的なスタイルは、最初の入力を最終的な出力に変換するために必要な機能の組み合わせを強調します。

この例では、手続き型コードと関数型コードの典型的な相対サイズも示しています。さらに、手続き型コードのパフォーマンス特性は、関数型コードのパフォーマンス特性よりもわかりやすい可能性があることを示しています。考慮してください: 関数はリスト内のすべての単語の長さを計算しますか? それとも、最初の偶数長の単語を見つけた直後に停止しますか? 一方、機能コードは、明示的なアルゴリズムではなく主に意図を表現するため、高品質の実装でかなり深刻な最適化を実行できます。

参考文献

この質問はよく出てきます...例を見てください:

John Backus のチューリング賞の講演では、関数型プログラミングの動機が非常に詳細に説明されています。

プログラミングはフォン・ノイマン・スタイルから解放できるか?

その論文については、現在の文脈では言及すべきではありません。本当に基本的なことだと思うので、我慢できませんでした。


補遺 - 2013

コメンテーターは、人気のある現代の言語は、手続き型および関数型に加えて、他のスタイルのプログラミングを提供していると指摘しています。このような言語は、多くの場合、次のプログラミング スタイルの 1 つ以上を提供します。

  • クエリ (例: リスト内包表記、言語統合クエリ)
  • データフロー (例: 暗黙の反復、一括操作)
  • オブジェクト指向 (カプセル化されたデータとメソッドなど)
  • 言語指向 (アプリケーション固有の構文、マクロなど)

この応答の疑似コードの例が他のスタイルから利用可能な機能のいくつかからどのように利益を得ることができるかの例については、以下のコメントを参照してください。特に、手続き型の例は、事実上すべての上位レベルの構造を適用することでメリットが得られます。

示されている例では、議論中の 2 つのスタイルの違いを強調するために、これらの他のプログラミング スタイルを意図的に混在させないようにしています。

于 2011-03-12T05:13:44.347 に答える
46

関数型プログラミングと命令型プログラミングの本当の違いは考え方です。命令型プログラマーは変数とメモリのブロックを考えていますが、関数型プログラマーは「入力データを出力データに変換するにはどうすればよいか」を考えています。「プログラム」はパイプラインです。データを入力から出力に変換する一連の変換。それは興味深い部分であり、「変数を使用しないでください」というビットではありません。

この考え方の結果として、FP プログラムは通常、それがどのように起こるかという特定のメカニズムではなく、が起こるかを記述します。これは強力です。 AsParallel() の場合と同じように、実装を自由に交換すると、シングルスレッド アプリが突然nコアにスケールアウトします。

于 2011-03-07T22:42:57.870 に答える
12
     Isn't that the same exact case for procedural programming?

いいえ、手続き型コードには副作用がある可能性があるためです。たとえば、呼び出し間の状態を保存できます。

とは言うものの、手続き型と見なされる言語でこの制約を満たすコードを書くことは可能です。また、機能していると見なされる一部の言語で、この制約を破るコードを作成することもできます。

于 2011-03-07T22:37:42.860 に答える
11

WReachの答えには同意しません。彼の答えを少し分解して、意見の相違がどこから来るのか見てみましょう。

まず、彼のコード:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

最初に注意すべきことは、彼が混同していることです。

  • 機能的
  • 表現志向と
  • イテレータ中心

プログラミング、および典型的な関数型スタイルよりも明示的な制御フローを持つ反復スタイル プログラミングの機能が欠落しています。

これらについて簡単に説明しましょう。

表現中心のスタイルは、物事が可能な限り物事に評価されるスタイルです。関数型言語は表現を好むことで有名ですが、実際には、構成可能な表現を持たない関数型言語を使用することも可能です。表現がなく、ステートメントだけの1 つを作成します。

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

これは、関数が純粋にステートメントとバインドのチェーンを介してチェーンされていることを除いて、前に示したものとほとんど同じです。

イテレータ中心のプログラミング スタイルは、Python で採用されているものかもしれません。純粋に反復的な、反復子中心のスタイルを使用しましょう。

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

各句は反復プロセスであり、スタック フレームの明示的な一時停止と再開によって結合されるため、これは機能しません。構文は部分的に関数型言語から着想を得ているかもしれませんが、完全に反復的な実装に適用されます。

もちろん、これを圧縮できます。

def all_odd(words):
    return all(odd(len(word)) for word in words)

命令は今ではそれほど悪くはありませんよね?:)

最後のポイントは、より明示的な制御フローについてでした。これを利用するために元のコードを書き直してみましょう。

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

イテレータを使用すると、次のことができます。

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

違いが次の場合、関数型言語のポイントは何ですか

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


関数型プログラミング言語の主な決定的な特徴は、典型的なプログラミング モデルの一部として突然変異を取り除くことです。これは、関数型プログラミング言語にはステートメントがない、または式を使用しないことを意味するとよく考えられますが、これらは単純化したものです。関数型言語は、明示的な計算を動作の宣言に置き換えます。これにより、言語はそれに対してリダクションを実行します。

この機能のサブセットに制限することで、プログラムの動作についてより多くの保証を得ることができ、これによりプログラムをより自由に構成することができます。

関数型言語を使用すると、通常、新しい関数を作成するのは、密接に関連する関数を作成するのと同じくらい簡単です。

all = partial(apply, and)

関数のグローバルな依存関係を明示的に制御していない場合、これは単純ではなく、おそらく不可能です。関数型プログラミングの最大の特徴は、より一般的な抽象化を一貫して作成し、それらを組み合わせてより大きな全体にできることを信頼できることです。

于 2014-09-07T17:26:13.503 に答える
6

手続き型パラダイム(代わりに「構造化プログラミング」と言いましょうか?)では、可変メモリと、それをある順序で(次々に)読み取り/書き込みする命令を共有しています。

関数型パラダイムには、変数と関数があります(数学的な意味では、変数は時間の経過とともに変化せず、関数は入力に基づいて何かを計算することしかできません)。

(これは単純化されすぎています。たとえば、FPLには通常、可変メモリを操作する機能がありますが、手続き型言語は高次の手続きをサポートできることが多いため、物事はそれほど明確ではありませんが、これでアイデアが得られるはずです)

于 2011-03-08T13:03:57.107 に答える
2

The Charming Python: IBM Developerworksの Pythonでの関数型プログラミングは、違いを理解するのに本当に役立ちました。

特に Python を少し知っている人にとっては、関数型プログラミングと手続き型プログラミングの違いを対比させたこの記事のコード例は、手続き型プログラミングと関数型プログラミングの違いを明確にすることができます。

于 2011-03-07T22:38:43.753 に答える
1

私は最近、表現の問題の観点から違いを考えています。 Phil Wadler の説明はよく引用されますが、この質問に対する受け入れられた回答の方がおそらく理解しやすいでしょう。基本的に、命令型言語は問題に対して 1 つのアプローチを選択する傾向があり、関数型言語は別のアプローチを選択する傾向があるようです。

于 2011-03-07T22:51:33.627 に答える