1. ホーム
  2. programming-languages

[解決済み] 手続き型と関数型の違いを真に理解するために

2022-05-15 08:21:17

質問

の違いを理解するのに苦労しています。 手続き的 機能的 というプログラミングパラダイムがあります。

のWikipediaの項目から最初の2つの段落を引用します。 関数型プログラミング :

コンピュータサイエンスにおいて、関数型 プログラミングのパラダイムであり 計算を数学的関数の評価として扱う 数学的関数の評価として扱い として扱い、状態や変更可能なデータを避けるプログラミングパラダイムです。関数型プログラミングは 関数の応用を重視する。 とは対照的に、関数の適用を重視します。 命令的なプログラミングスタイルであり 関数の適用を重視します。 関数型プログラミングのルーツは ラムダ計算をルーツとしています。 ラムダ計算とは、1930年代に開発された 関数の定義、関数 関数型プログラミングのルーツはラムダ計算です。多くの 関数型プログラミング言語の多くは 多くの関数型プログラミング言語は、ラムダ計算を発展させたものと見なすことができる。 ラムダ計算の発展型とみなすことができる。

実際のところ、数学的な関数と 数学的関数と との違いは、命令型プログラミングで使用される とは異なり、命令型 関数が副作用を持つことがあるということです。 命令型関数は副作用を持ち、プログラムの状態を変化させることができます。 このため、関数には参照透過性がありません。 つまり、同じ言語表現でありながら 式が、その時々によって異なる値になることがあります。 つまり、同じ言語表現が、実行中のプログラムの状態によって、異なる時に異なる値 ということです. 逆に、関数型コードでは 関数の出力値は、入力された引数にのみ依存します。 関数の出力値は、関数に入力された引数にのみ依存します。 にのみ依存するので、関数 f を同じ値で2回呼び出すと 引数 x は同じ 結果 f(x) となります。副作用をなくすことで 副作用を排除することで、より簡単に プログラムの挙動を理解し予測することが プログラムの挙動を理解し、予測することが容易になります。 関数型プログラミングの開発動機の一つである 関数型プログラミングの重要な動機のひとつです。

第2段落で、次のように書かれています。

<ブロッククオート

逆に、関数型コードでは、関数の出力値は関数に入力される引数だけに依存するので、関数を呼び出すと f を2回呼び出すと、引数に同じ値を持つ x は同じ結果になります。 f(x) となります。

手続き型プログラミングも全く同じケースではないでしょうか?

手続き型と関数型では、どのような点に注目すれば際立つのでしょうか?

どのように解決するのですか?

関数型プログラミング

関数型プログラミングとは、関数を値として扱えるようにすることです。

通常の値とのアナロジーを考えてみましょう。 2つの整数の値を取り、それを + 演算子を使って結合し、新しい整数を得ることができます。 また、整数に浮動小数点数を掛けて、浮動小数点数を得ることもできます。

関数型プログラミングでは、次のような演算子を使って2つの関数値を組み合わせて新しい関数値を生成することができます。 コンポーズ または 持ち上げる . また、関数値とデータ値を組み合わせて、次のような演算子で新しいデータ値を生成することもできます。 マップ または 折りたたみ .

多くの言語が関数型プログラミングの機能を持っていることに注意してください--通常は関数型言語と考えられていない言語でさえも。 祖先である 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)oddlength 関数を使って、文字列の長さが奇数かどうかを判断する新しい関数を生成します。
  2. map(..., words) の各要素に対してその新しい関数を呼び出します。 words の各要素に対してその新しい関数を呼び出し、最終的に、対応する単語の文字数が奇数であるかどうかを示す、新しいブール値のリストを返します。
  3. apply(and, ...) は結果のリストに "and"演算子を適用します。 -を適用し、すべてのブール値をまとめて最終結果を得ます。

これらの例から、手続き型プログラミングは変数の中で値を動かし、最終的な結果を得るために必要な操作を明示的に記述することに非常に重点を置いていることがわかります。 これに対して関数型は、最初の入力を最終的な出力に変換するために必要な関数の組み合わせに重点を置いています。

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

さらなる読み物

この質問はよく出てきます...例えば、以下をご覧ください。

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

プログラミングはフォン・ノイマン流から解放されるか?

この論文はかなり専門的で、すぐに終わってしまうので、本当は今の文脈では触れないほうがいいのですが。 本当に基礎的なものだと思うので、我慢できなかったのです。


追記 - 2013年

現代の人気のある言語では、手続き型や関数型以外にもプログラミングのスタイルがあることをコメンテーターが指摘しています。 そのような言語では、しばしば以下のようなプログラミングスタイルが提供されています。

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

この回答における擬似コードの例が、それらの他のスタイルから利用可能な機能の一部からどのように恩恵を受けることができるかについては、以下のコメントを参照してください。 特に、手続き的な例は、事実上あらゆる上位レベルの構成要素の適用から恩恵を受けるでしょう。

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