関数型プログラミング言語と命令型プログラミング言語の違いは何ですか?


159

C#、Visual Basic、C ++、Javaなどのオブジェクト指向プログラミング(OOP)言語を含むほとんどの主流言語は、主に命令型(手続き型)プログラミングをサポートするように設計されていますが、Haskell / goferのような言語は純粋に機能的です。これらの2つのプログラミング方法の違いは何ですか?

プログラミングの方法を選択することはユーザーの要件に依存することはわかっていますが、関数型プログラミング言語を学ぶことが推奨されるのはなぜですか?



1
この他の[投稿] [1]を確認してください。違いを明確に説明しています。[1]:stackoverflow.com/questions/602444/...
シータ

回答:


160

定義: 命令型言語は一連のステートメントを使用して、特定の目標を達成する方法を決定します。これらのステートメントは、それぞれが順番に実行されるときにプログラムの状態を変更すると言われています。

例: Javaは命令型言語です。たとえば、一連の数値を追加するプログラムを作成できます。

 int total = 0;
 int number1 = 5;
 int number2 = 10;
 int number3 = 15;
 total = number1 + number2 + number3; 

各ステートメントは、各変数への値の割り当てからそれらの値の最後の加算まで、プログラムの状態を変更します。5つのステートメントのシーケンスを使用して、プログラムは数値5、10、および15を加算する方法を明示的に通知されます。

関数型言語: 関数型プログラミングのパラダイムは、問題解決のための純粋な関数型アプローチをサポートするために明示的に作成されました。関数型プログラミングは、宣言型プログラミングの一種です。

純粋関数の利点: 関数変換を純粋関数として実装する主な理由は、純粋関数が構成可能であること、つまり、自己完結型でステートレスであることです。これらの特性には、次のような多くの利点があります。可読性と保守性の向上。これは、各関数がその引数を指定して特定のタスクを実行するように設計されているためです。関数は外部状態に依存しません。

より簡単な繰り返し開発。コードはリファクタリングが容易であるため、設計の変更は多くの場合、実装が容易です。たとえば、複雑な変換を記述し、変換でいくつかのコードが数回繰り返されていることに気付いたとします。純粋なメソッドを介してリファクタリングする場合、副作用を心配することなく、自由に純粋なメソッドを呼び出すことができます。

簡単なテストとデバッグ。純粋な関数は分離してより簡単にテストできるため、一般的な値、有効なエッジケース、無効なエッジケースで純粋な関数を呼び出すテストコードを記述できます。

OOP Peopleまたは命令型言語の場合:

オブジェクト指向言語は、物事に一定の操作セットがあり、コードが進化するにつれて主に新しい物を追加する場合に適しています。これは、既存のメソッドを実装する新しいクラスを追加することで実現でき、既存のクラスはそのままになります。

関数型言語は、固定されたものがあり、コードが進化するにつれて、主に既存のものに新しい操作を追加する場合に適しています。これは、既存のデータ型で計算する新しい関数を追加することで達成でき、既存の関数はそのままになります。

短所:

プログラミングの方法を選択することはユーザーの要件に依存するため、ユーザーが適切な方法を選択しない場合にのみ害が生じます。

進化がうまくいかない場合、問題があります:

  • オブジェクト指向プログラムに新しい操作を追加するには、多くのクラス定義を編集して新しいメソッドを追加する必要がある場合があります
  • 関数型プログラムに新しい種類のものを追加するには、多くの関数定義を編集して新しいケースを追加する必要がある場合があります。

10
この場合の純関数は、数学関数と同等です。同じ入力は常に同じ出力にマッピングされます。また、(1つまたは複数の値を返す以外に)副作用がないため、コンパイラーはいくつかの優れた最適化を行うことができ、競合するものがないため、関数を並列で実行することが容易になります。
WorBlux 2013

それで、保守可能でテスト可能なoopアプリケーションを構成する正しい方法とベストプラクティスは、落胆した状態の命令型コードを設計する傾向がありますか?
KemalGültekin2014年

4
各プログラミングの特徴が強調されているテキストに明確な違いはありません。手続き型プログラミングの説明のほとんどは、命令型プログラミングテキストで交換でき、その逆も可能です。
AxeEffect 2015

7
この回答は、関数型プログラミングとは何かを明確にすることを目的としていますが、純粋な関数とは何かを定義することさえしません。この答えを誰がどのように読んで、宣言型プログラミングと手続き型プログラミングの違いを理解することに自信を持つことができるのかわかりません。
リンゴ

230

ここに違いがあります:

必須:

  • 開始
  • 靴のサイズを9 1/2にします。
  • キーの配列[7]を保持するためにポケットにスペースを作ります。
  • 部屋の鍵をポケットに入れて鍵を入れます。
  • ガレージに入ります。
  • ガレージを開きます。
  • 車に入ります。

...などなど...

  • 牛乳を冷蔵庫に入れます。
  • やめる。

宣言型、その機能はサブカテゴリです。

  • 乳糖の消化に問題がない限り、牛乳は健康的な飲み物です。
  • 通常、人は牛乳を冷蔵庫に保管します。
  • 冷蔵庫は、物を冷やしておく箱です。
  • 店舗とは、商品を販売する場所です。
  • 「販売」とは、物事を金銭で交換することを意味します。
  • また、金銭のやりとりを「買い」といいます。

...などなど...

  • 冷蔵庫に牛乳があることを確認してください(必要なとき-遅延関数型言語の場合)。

概要:命令型言語では、メモリ内のビット、バイト、およびワードを変更する方法とその順序をコンピュータに指示します。機能的なものでは、物事やアクションなどをコンピュータに伝えます。たとえば、0の階乗は1であり、他のすべての自然数の階乗はその数とその前任者の階乗の積です。言わない:nの階乗を計算するには、メモリ領域を予約して1を格納し、そのメモリ領域の数値に2からnまでの数値を掛けて、同じ場所と最後に結果を格納します。メモリ領域には階乗が含まれます。


1
ありがとうございました。それはそれを見るのに最適な方法です。
L-Samuels 2014

5
私はあなたの説明@Ignoが好きでしたが、何かはまだ私には不明瞭です。宣言型では、物事を伝えるだけでも、適切に処理するにはビットを変更し、マシンの状態を変更する必要があります。どういうわけかDeclarativeは手続き型プログラミング(C関数など)に似ていますが、内部的には大きな違いがあります。C関数、関数型プログラミング(マシンレベル)の関数と同じではありませんか?
phoenisx

11
@ Igno、Subrotoのように、私はあなたの説明を本当に理解していません。あなたが書いたものは次のように要約できるようです:答えが必要...答えを得なさい。どのように重要なビットを無視するようです。その部分をユーザーから隠す方法がわかりません。ある時点で、誰かがそれがどのように行われたかを知らなければなりません...ウィザードをカーテンの後ろに永久に置くことはできません。
ブレット・トーマス

3
これは私が関数型プログラミングであると私が理解しているものではありません。関数型プログラミングは、隠れた入力と出力を関数から削除することだと思いました。
リンゴ

7
複雑な説明。
JoeTidee

14

最近のほとんどの言語は、命令型と関数型の両方で程度は異なりますが、関数型プログラミングをよりよく理解するには、java / c#などの関数型言語ではない命令型コードとは対照的に、Haskellのような純粋な関数型言語の例を取り上げるのが最善です。例で説明するのはいつでも簡単だと思いますので、以下はその1つです。

関数型プログラミング:nの階乗、つまりnを計算します!つまり、nx(n-1)x(n-2)x ... x 2 X 1

-- | Haskell comment goes like
-- | below 2 lines is code to calculate factorial and 3rd is it's execution  

factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3

-- | for brevity let's call factorial as f; And x => y shows order execution left to right
-- | above executes as := f(3) as 3 x f(2) => f(2) as 2 x f(1) => f(1) as 1 x f(0) => f(0) as 1  
-- | 3 x (2 x (1 x (1)) = 6

Haskelでは、引数値のレベルまで関数をオーバーロードできることに注意してください。以下は、命令性の度合いが増す命令コードの例です。

//somewhat functional way
function factorial(n) {
  if(n < 1) {
     return 1;
  }
  return n * factorial(n-1);   
}
factorial(3);

//somewhat more imperative way
function imperativeFactor(n) {
  int f = 1
  for(int i = 1; i <= n; i++) {
     f = f * i
  }
  return f;
}

この読み取りは、命令型コードがどのように部分、マシンの状態(i in forループ)、実行順序、フロー制御に重点を置いているかを理解するための良い参考になります。

後者の例は、大まかにjava / c#langコードと見なすことができ、最初の部分は、Haskellが値(ゼロ)で関数をオーバーロードするのとは対照的に、言語自体の制限と見なすことができるため、純粋な関数型言語ではないと言えます。あなたはそれが機能的progをサポートすると言うことができます。ある程度。

開示:上記のコードはどれもテスト/実行されていませんが、うまくいけば、コンセプトを伝えるのに十分なはずです。また、そのような修正についてコメントをいただければ幸いです:)


1
そうじゃないのreturn n * factorial(n-1);
jinawee '20

@jinawee、指摘してくれてありがとう、私はそれを修正しましたn * (n-1)
古い僧侶

10

関数型プログラミングは宣言型プログラミングの一種であり、計算のロジックを記述し、実行順序は完全に重視されていません。

問題:この生物を馬からキリンに変えたい。

  • 首を長くする
  • 脚を伸ばす
  • スポットを適用する
  • 生き物に黒い舌を与える
  • 馬の尾を取り除く

各項目を任意の順序で実行して、同じ結果を生成できます。

命令型プログラミングは手続き型です。状態と順序は重要です。

問題:車を駐車したい。

  1. ガレージドアの初期状態に注意してください
  2. 私道で車を止める
  3. ガレージのドアが閉まっている場合は、ガレージのドアを開けて、新しい状態を覚えておいてください。それ以外の場合は続行します
  4. 車を車庫に入れます
  5. ガレージのドアを閉じる

各ステップは、目的の結果に到達するために実行する必要があります。ガレージのドアが閉まっている状態でガレージに引っ張ると、ガレージのドアが壊れます。


非同期と同期の違いだけがわかります。
Vladimir Vukanac

@VladimirVukanac async / syncはメカニズムであり、プログラミングの形式ではありません
Jakub Keller

2
ああ、ありがとう。もっと詳しく調べます。問題1を問題2と同じ「車を駐車したい」に更新するが、関数型プログラミングの方法で記述してくれませんか。その後、並列処理は除外されます。
Vladimir Vukanac

6

関数型プログラミングは「関数を使用したプログラミング」であり、関数には、参照の透過性など、いくつかの予期される数学的特性があります。これらのプロパティから、追加のプロパティが流れます。特に、数学的証明につながる代替可能性によって有効になる一般的な推論ステップ(つまり、結果の信頼性の正当化)が流れます。

したがって、関数型プログラムは単なる表現にすぎません。

式が参照として透過的ではなくなった(したがって、関数と値で構築されず、それ自体が関数の一部になることができない)命令型プログラムの場所に注目することで、2つのスタイルのコントラストを簡単に確認できます。最も明白な2つの場所は次のとおりです。変異(例:変数)その他の副作用非ローカル制御フロー(例:例外)

関数と値で構成されるプログラムとしてのこのフレームワークでは、言語、概念、「関数型パターン」、コンビネーター、およびさまざまな型システムと評価アルゴリズムの実用的なパラダイム全体が構築されます。

最も極端な定義では、ほとんどすべての言語(CやJavaを含む)を関数型と呼ぶことができますが、通常、用語は、特に関連する抽象化(クロージャー、不変値、パターンマッチングなどの構文支援など)を備えた言語用に予約されています。関数型プログラミングの使用に関する限り、関数の使用を含み、副作用なしでコードをビルドします。証明を書くために使用


3

命令型プログラミングスタイルは、2005年から2013年までWeb開発で実践されていました。

命令型プログラミングを使用して、アプリケーションが実行する必要があることを正確にリストしたコードを段階的に作成しました。

関数型プログラミングスタイルは、関数を組み合わせる巧妙な方法によって抽象化を生み出します。

答えの中で宣言型プログラミングについての言及があり、それに関して宣言型プログラミングは従うべきいくつかのルールをリストアップしていると言います。次に、初期状態として参照するものをアプリケーションに提供し、それらのルールにアプリケーションの動作を定義させます。

さて、これらの簡単な説明はおそらくあまり意味をなさないので、類推を通して、命令型プログラミングと宣言型プログラミングの違いを見てみましょう。

ソフトウェアを構築しているのではなく、生計を立てているパイを想像してみてください。おそらく私たちは悪いパン屋であり、おいしいパイを私たちが本来あるべき方法で焼く方法を知りません。

だから私たちの上司は私たちに私たちがレシピとして知っている方向のリストをくれます。

レシピはパイの作り方を教えてくれます。1つのレシピは、次のような命令スタイルで記述されます。

  1. 小麦粉1カップを混ぜる
  2. 卵を1個加える
  3. 砂糖1カップを加える
  4. 鍋に混合物を注ぎます
  5. オーブンにオーブンを30分間、華氏350度置きます。

宣言的レシピは次のことを行います。

小麦粉1カップ、卵1個、砂糖1カップ-初期状態

ルール

  1. すべてが混ざっている場合は、鍋に入れます。
  2. すべてが混ざっていない場合は、ボウルに入れます。
  3. すべてを鍋に入れたら、オーブンに入れます。

したがって、必須のアプローチは、段階的なアプローチによって特徴付けられます。ステップ1から始めて、ステップ2などに進みます。

最終的に、最終製品がいくつか得られます。このパイを作るには、これらの材料を混ぜ合わせ、鍋とオーブンに入れれば、最終製品ができあがります。

宣言型の世界では違います。宣言型のレシピでは、レシピを2つの部分に分け、最初は変数などのレシピの初期状態をリストする1つの部分から始めます。したがって、ここでの変数は、成分の量とそのタイプです。

私たちは初期状態または初期成分を取り、それらにいくつかのルールを適用します。

したがって、私たちは初期状態を取り、ルバーブストロベリーパイなどを食べる準備ができるまで、これらのルールを何度も繰り返します。

したがって、宣言型アプローチでは、これらのルールを適切に構成する方法を知る必要があります。

だから、私たちが私たちの成分や状態を調べたいと思うかもしれない規則は、混合されている場合、それらをフライパンに入れます。

当初の状態では、まだ材料を混ぜていないため、これは一致しません。

したがって、ルール2は、それらが混合されていない場合は、ボウルで混合します。はい、このルールが適用されます。

これで、状態として混合成分のボウルができました。

次に、その新しい状態をルールに再度適用します。

したがって、ルール1は、材料が混合されている場合、それらをフライパンに入れると言います。そうです、今はルール1が適用されるので、実行してみましょう。

これで、材料が混合されて鍋に入れられた新しい状態になりました。ルール1はもはや関係がなく、ルール2は適用されません。

ルール3は、材料がフライパンにある場合はオーブンに入れます。この新しいルールは、この新しい状態に適用されるものです。

そして、美味しいホットアップルパイなどになってしまいます。

さて、あなたが私のようであれば、あなたは考えているかもしれません、なぜ私たちはまだ命令型プログラミングをしていないのですか?意味あり。

まあ、単純なフローの場合はそうですが、ほとんどのWebアプリケーションには、命令型プログラミング設計では適切にキャプチャできないより複雑なフローがあります。

宣言的アプローチでは、次のようないくつかの初期成分または初期状態がある場合があります textInput=“”、単一の変数のます。

たぶん、テキスト入力は空の文字列として始まります。

この初期状態を取得して、アプリケーションで定義された一連のルールに適用します。

  1. ユーザーがテキストを入力した場合は、テキスト入力を更新します。さて、今は当てはまりません。

  2. テンプレートがレンダリングされる場合は、ウィジェットを計算します。

  3. textInputが更新された場合は、テンプレートを再レンダリングしてください。

まあ、これは当てはまらないので、プログラムはイベントが発生するのを待つだけです。

したがって、ある時点でユーザーがテキスト入力を更新し、ルール番号1を適用する場合があります。

更新する場合があります “abcd”

したがって、textとtextInputの更新を更新しました。ルール番号2は適用されません。ルール番号3は、テキスト入力が更新された場合に発生したもので、テンプレートを再レンダリングしてから、ルール2に戻ります。 、ウィジェットを計算します。さあ、ウィジェットを計算しましょう。

一般に、プログラマーとして、より宣言的なプログラミング設計を目指しています。

命令型の方がより明確で明白に見えますが、宣言型のアプローチは、より大きなアプリケーションに非常にうまく対応します。


2

•命令型言語:

  • 効率的な実行

  • 複雑なセマンティクス

  • 複雑な構文

  • 並行性はプログラマー設計です

  • 複雑なテスト、参照透過性なし、副作用あり

  • 状態あり

•関数型言語:

  • 単純なセマンティクス

  • 単純な構文

  • 効率の低い実行

  • プログラムを自動的に並行させることができます

  • シンプルなテスト、参照透過性、副作用なし

  • 状態はありません

1

関数型プログラミングを命令型で表現することは可能だと思います:

  • オブジェクトとif... else/ switchステートメントの 多くの状態チェックを使用する
  • 非同期性を処理するいくつかのタイムアウト/待機メカニズム

そのようなアプローチには大きな問題があります:

  • ルール/手順が繰り返されている
  • ステートフルネスは、副作用/ミスの可能性を残します

オブジェクトのような関数/メソッドを扱い、ステートレス性を採用する関数型プログラミングは、私が信じているこれらの問題を解決するために生まれました。

使用例:Android、iOSなどのフロントエンドアプリケーション、またはウェブアプリのロジックを含む。バックエンドとの通信。

命令型/手続き型コードで関数型プログラミングをシミュレートする際のその他の課題:

  • レース状態
  • イベントの複雑な組み合わせとシーケンス。たとえば、ユーザーは銀行アプリで送金しようとします。ステップ1)次のすべてを並行して行い、すべてが良好な場合にのみ続行するa)ユーザーがまだ良好かどうかを確認する(詐欺、AML)b)ユーザーに十分なバランスがあるかどうかを確認するc)受信者が有効で良好かどうかを確認する(詐欺、 AML)など。ステップ2)転送操作を実行します。ステップ3)ユーザーの残高および/または何らかの追跡の更新を表示します。たとえばRxJavaでは、コードは簡潔で賢明です。それがなければ、多くのコード、乱雑でエラーが発生しやすいコードがあると想像できます

結局のところ、関数型コードは、コンパイラーによって命令型/手続き型であるアセンブリーコードまたはマシンコードに変換されると考えています。ただし、アセンブリを作成しない限り、人間は高レベル/人間が読める言語でコードを書くため、リストされているシナリオでは、関数型プログラミングがより適切な表現方法です。


-1

私はこの質問が古いことを知っており、他の人がすでにそれをうまく説明しているので、簡単な言葉で同じことを説明する問題の例を挙げたいと思います。

問題:1のテーブルを書き込んでいます。

解決: -

命令型スタイル:=>

    1*1=1
    1*2=2
    1*3=3
    .
    .
    .
    1*n=n 

機能的なスタイル:=>

    1
    2
    3
    .
    .
    .
    n

命令型の説明では、命令をより明確に記述し、より簡単な方法で呼び出すことができます。

関数型スタイルの場合と同様に、説明が必要ないものは無視されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.