web-dev-qa-db-ja.com

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

私の関数が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});

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

9
rahulaga_dev

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

関数型プログラミングは原則として declarative です。 what と言うと、結果を how の代わりに計算します。

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

predsum pred numbers = sum (filter pred numbers)

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

sumfilterを使用するのはトリックであり、重要ではないと言うことができます。次に、これらのヘルパーなしで実装してみましょう(最善の方法は、最初にそれらを実装することです)。

sumを使用しない "Functional Programming 101"ソリューションは再帰を伴います:

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

それはまだかなり明確です what は単一の関数呼び出しに関する結果です。どちらか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ステートメントなどを追加すると、追跡が突然難しくなります。すべてのコードは約 how であり、 what は読者に任されています理解する-これは明らかに非常に必須のスタイルです

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

番号。

それらによって説明するのがはるかに簡単な多くのものと、高速であるために可変状態を必要とする多くのアルゴリズムがあります。しかし、変数は本質的に必須であり、 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))

これらの関数式は、その種の問題にうまく適合し、コードを短くします(そして 短い方が良い )。命令型のコードを不用意に置き換えないでください。ただし、適合する場合は、ほとんどの場合、これらの方が適しています。

16
Frax

関数型プログラミングでは、可変状態を使用することはお勧めしません。ループは可変状態との組み合わせでのみ有用であるため、ループは結果として推奨されません。

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

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

9
JacquesB

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

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

7
Philip Kendall