関数型プログラミングで代入演算子またはループの使用が推奨されないのはなぜですか?


9

私の関数が2つの要件を満たしている場合、Sum リスト内の項目の合計を返す関数は、特定の条件に対して項目がtrueと評価され、純粋な関数と呼ばれる資格があると思いますか?

1)特定のi / pのセットに対して、関数が呼び出された時間に関係なく、同じo / pが返されます

2)副作用はありません

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

例: Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

私がこの質問をしている理由は、必須のプログラミングスタイルであるため、代入演算子とループを回避するようアドバイスしている人がほとんどいるためです。では、関数プログラミングのコンテキストでループと代入演算子を使用する上記の例で何が問題になるのでしょうか?


1
itemループ内で変数が変更された場合、副作用はありません -副作用があります。
ファビオ

@ファビオ大丈夫。しかし、副作用の範囲について詳しく説明できますか?
rahulaga_dev

回答:


16

関数型プログラミングの違いは何ですか?

関数型プログラミングは原則として宣言型です。計算方法ではなく、結果が何であるかを言います。

スニペットの実際に機能する実装を見てみましょう。Haskellでは次のようになります。

predsum pred numbers = sum (filter pred numbers)

それは明らかである結果は?かなりそうです、それは述語を満たす数の合計です。どのように計算されますか?私は気にしません、コンパイラに尋ねます。

あなたは、おそらく使用していることを言うことができるsumfilterトリックであり、それはカウントされません。これらのヘルパーなしで実装してみましょう(最善の方法は最初に実装することです)。

使用しない「Functional Programming 101」ソリューションsumは、再帰によるものです。

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

単一の関数呼び出しに関して、結果がどうなるかはまだはっきりしてます。それはどちらかである0、またはrecursive call + h or 0に応じて、pred h。最終結果がすぐに明らかではない場合でも、かなり単純です(ただし、少し練習するだけで、forループのように読み取れます)。

それをあなたのバージョンと比較してください:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

結果はどうですか?ああ、わかりました:単一のreturnステートメント、ここに驚きはありません:return result

しかし、何resultですか?int result = 0?正しくないようです。あなたはそれで後で何かをし0ます。OK、あなたはそれにitemsを追加します。等々。

もちろん、ほとんどのプログラマーにとって、これはこのような単純な関数で何が起こるかは非常に明白ですが、追加のreturnステートメントなどを追加すると、突然追跡が難しくなります。すべてのコードはどのように、そして読者が理解するために残されているものについてです- これは明らかに非常に必須のスタイルです。

それで、変数とループは間違っていますか?

番号。

彼らによってはるかに簡単に説明できるものがたくさんあり、可変状態を高速にするために必要な多くのアルゴリズムがあります。しかし、変数は本質的に必須であり、whatではなくhowを説明、数行後または数回のループ反復後にそれらの値が何であるかをほとんど予測しません。ループは一般的に意味を成すために状態を必要とするので、本質的に必須です。

変数とループは、単に関数型プログラミングではありません。

概要

現代の関数型プログラミングは、パラダイムというよりは、少しスタイルがあり、便利な考え方です。純粋な関数を強く好むのはこの考え方ですが、実際にはほんの一部にすぎません。

ほとんどの一般的な言語では、いくつかの関数構成を使用できます。たとえばPythonでは、次のいずれかを選択できます。

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

または

return sum(filter(pred, numbers))

または

return sum(n for n in numbers if pred(n))

これらの関数式は、その種の問題にうまく適合し、コードを短くします(短くするのが良いです)。命令型コードを不用意にそれらに置き換えるべきではありませんが、それらが適合する場合、ほとんどの場合、それらはより良い選択です。


素敵な説明のためのTHX !!
rahulaga_dev 2018年

1
@RahulAgarwal この答えは興味深いかもしれませんが、ステップを説明するのではなく、真理を確立するという接線の概念をうまく捉えています。また、「宣言型言語には副作用が含まれ、命令型言語には副作用がない」というフレーズも好きです。通常、関数型プログラムは、外界を扱う(または最適化されたアルゴリズムを実行する)ステートフルコードと純粋に関数型のコードの間に明確で非常に目に見えるカットがあります。
FRAX

1
@Frax:ありがとう!! 私はそれを徹底的に調べます。また最近、価値の価値に関するリッチヒッキーの話に出会いました。それは本当に素晴らしいです。私は1つの経験則-「値を保持する何かを操作するのではなく、値と式を操作して変更できる」
rahulaga_dev

1
@Frax:また、FPは命令型プログラミングを抽象化したものだと言っても過言ではありません。結局のところ、誰かが「方法」についてマシンに指示する必要があるからです。もしそうなら、命令型プログラミングはFPと比較してより低レベルの制御を持っているのではないですか?
rahulaga_dev 2018年

1
@Frax:根本的なマシンに近いという意味で、命令はより低いレベルであるというRahulに同意します。ハードウェアが無料でデータのコピーを作成できれば、効率を向上させるために破壊的な更新を行う必要はありません。この意味で、命令型パラダイムは金属に近いものです。
ジョルジオ

9

関数型プログラミングでは、一般に可変状態の使用はお勧めしません。ループは可変状態との組み合わせでのみ役立つため、結果としてループは推奨されません。

全体としての関数は純粋です。これは素晴らしいことですが、関数型プログラミングのパラダイムは関数全体のレベルでのみ適用されるわけではありません。また、関数内のローカルレベルでも、変更可能な状態を回避したいとします。そして、推論は基本的に同じです:可変状態を回避することで、コードが理解しやすくなり、特定のバグを防ぎます。

あなたの場合、あなたnumbers.Where(predicate).Sum()は明らかにはるかに簡単なものを書くことができます。そして、よりシンプルなことはバグが少ないことを意味します。


どうも !!私は印象的なラインが欠けていたと思います- しかし、関数型プログラミングのパラダイムは、関数全体のレベルで適用されるだけではありませんが、この境界をどのように視覚化するかについても疑問に思っています。基本的に消費者の観点からは純粋な関数ですが、この関数を実際に記述した開発者は純粋な関数のガイドラインに従っていませんか?混乱:(
rahulaga_dev '21 / 10/21

@RahulAgarwal:どんな境界?
JacquesB 2018年

関数の観点から見ると、プログラミングパラダイムがFPになる資格があるかどうか、私は混乱していますか?Bcoz私はの実装の実装を見ればWherenumbers.Where(predicate).Sum()-それはを使用するforeachループ。
rahulaga_dev 2018年

3
@RahulAgarwal:関数のコンシューマーとして、関数またはモジュールが外部的に純粋である限り、関数またはモジュールが内部的に変更可能な状態を使用するかどうかは気にしません。
JacquesB 2018年

7

外部オブザーバーの観点からすると、あなたのSum関数は純粋ですが、内部実装は明らかに純粋ではresultありません。繰り返し変化する状態が保存されています。ミュータブルな状態を回避する理由の1つは、プログラマーの認知的負荷が大きくなり、その結果、より多くのバグにつながるためです[要出典]

このような単純な例では、格納される変更可能な状態の量はおそらく深刻な問題を引き起こさないほど小さいですが、一般的な原則は依然として適用されます。のようなおもちゃの例Sumは、関数型プログラミングが命令型よりも優れていることを説明する最良の方法ではありません。多くの変更可能な状態で何かを試してみてください。利点が明らかになる場合があります。

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