ループ副作用の関数型プログラミング


8

関数内にローカル変数またはforループがあることが純粋な関数型プログラミングと見なされないのはなぜか、頭を回そうとしています。

この関数を考えると:

int as_int(char *str)
{
    int acc; /* accumulate the partial result */

    for (acc = 0; isdigit(*str); str++) {
        acc = acc * 10 + (*str - '0');
    }

    return acc;
}

変数accはどのような状況で副作用になりますか?並行環境でも、関数の呼び出しごとに独自のaccのコピーがあります。そのため、関数型プログラミングで許可されていない理由がよくわかりません。



5
as_int純粋な関数ですが、その中のコードは純粋ではありません。
Doval

1
純粋なコードは通常、不変変数に関連付けられています。acc変更可能です。
Florian Margaine、2014

回答:


11

関数型プログラミングでループするような制御文で行われていないforwhile、それが明示的のような関数への呼び出しで行われますmapfoldまたは再帰-内部ループコールをかけ伴うすべてが別の関数内。ループコードがループの外側の変数を変更する場合、この内側のループ関数はスコープ外の変数を操作するため、不純になります。したがって、外部関数全体は純粋ですが、ループはそうではありません。関数型プログラミングのループ構造では、状態を明示的にする必要があります。関数型プログラミングループツールを使用してコードを何かに変換すると、不正確さが明らかになります。

int as_int(char *str)
{
    int acc = 0; /* accumulate the partial result */

    map(takeWhile(isdigit, str), void function(char *chr) {
      acc = acc * 10 + (chr - '0');
    });

    return acc;
}

(注-この構文は、全体的な考え方を理解するために概算です)

このコードは、accスコープの外にある変数を変更する必要があるループ本体の内部関数を使用します。これは不正確です。内側のループ関数は外側のループコンテキストに依存します。同じ文字で複数回呼び出すと、副作用があり、文字のシーケンスで呼び出す順序が重要になります。関数型プログラミングでは、これを純粋な関数にするために、ループの反復間で渡される状態にこの依存関係を明示的に指定する必要がありfoldます。

int as_int(char *str)
{
    return fold(takeWhile(isdigit, str), 0, int function(char *chr, int acc) {
      return acc * 10 + (chr - '0');
    });
}

foldは、内部ループ本体に2つの引数の関数を使用します。最初の引数foldはループしているシーケンス内の項目です。2番目の引数は、内部ループ本体が部分的な結果を作成するために使用する値です。最初のループの反復の場合accは0、2番目の場合accは最初の内部ループ関数呼び出しが返されたもの、3番目の場合は2番目の内部ループが返されたもの、最後のループはfold式全体の結果を返します。

これは、プログラムの残りの部分から見たコードの問題ではないことに注意してくださいas_int。両方の定義は純粋です。違いは、内部ループコードを純粋な関数にすることです。関数型プログラミングが提供する膨大な数のツールを利用して、ループをより宣言的なものに分解できます(takeWhile、fold、filter、mapなどを使用します)。


6
+1。などの高次機能を使用することの利点の中でtakeWhilefoldfiltermap、(つまり、宣言型スタイル)あなたはまた、「破壊的に更新メモリ位置によって計算何か」という点で思考を停止することです。このように、結果は履歴/計算ステップの正確なシーケンスに依存しません。
Giorgio 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.