私はc#を使用してFPを学習しようとしています。 FPのコンテキストでは、基本的な代入ステートメントまたはreturnステートメントの使用は合成可能とは見なされないため、FPパラダイムではその使用を推奨していません。
私はすでに発見しました この古いStackoverflowの質問「関数型プログラミングのコンテキストでの構成可能性は何を意味しますか?」 ですが、質問(およびその回答)は、基本的なステートメントではなく、制御構造、スレッド、およびモナドに焦点を当てています。
副作用のある関数 についても以前の質問をしましたが、ここでの新しい質問は、純粋でない関数については特に言及していません。本質的に同じ質問の場合でも、ステートメント/命令を持つ関数は不純と見なされるということですか?
誰かがこれをいくつかの例(できれば割り当てまたはreturnステートメントを使用したいくつかの例)でこれをよりよく理解するのを助けることができますか?
上記のニースのコンパクトな答えはかなりあります。ただし、FPに関するいくつかの関連する質問をするので、役立つと思われる場合は、さらに説明を提案します。
関数型プログラミング は、すべてを宣言または式として扱います。このパラダイムでは、関数は抽象化の主な手段です。関数は式(合成)で結合され、それ自体が式のオペランド/引数になり、 高次関数 の結果になる場合があります。
運転のアイデアは、マシンに次のことを実行するように命令するシーケンシャルパラダイムから離れることです。これは、C#のようなステートメント指向言語では理解しにくいものです。次の簡単な計算を見てください。
double res; // computes something based on previously defined a and b
if (a>b)
res = Math.Sqrt( a*a - b*b );
else
res = - Math.Sqrt ( b*b - a*a);
このif
ステートメントは構成できません。計算の結果を別の計算で使用する場合は、res
を使用する新しいステートメントを追加する必要があります。つまり、副作用に依存しています。
res = res *2; // another side effect
if (res > 100)
...
次の自然な方法は、ステートメントを関数に抽出することです。 THisは、残りのコードに影響を与えない副作用をローカル変数に分離します。
double f(double a, double b) { // abstract calculation in a function
double res;
if (a>b)
res = Math.Sqrt( a*a - b*b );
else
res = - Math.Sqrt ( b*b - a*a);
return res;
}
double growth(double x) {
return x*2;
}
その後、関数を簡単に作成できます。
if (growth(f(a,b))>100)
...
また、関数を式として可能な限り表現するようにして、副作用に依存しないようにすることもできます。ただし、長い式も書き込みや読み取りが難しいため、注意してください。
double f(double a, double b) { // abstract calculation in a function
return a>b ? Math.Sqrt( a*a - b*b ) : - Math.Sqrt ( b*b - a*a);
}
C#では、より機能的なスタイルを使用することもできます。これにより、問題をより簡単な関数(ここでは名前空間を汚染しないローカル関数)に分解し、構成を使用することが容易になります。
Func<double, double, double> f = (a,b) => (a>b ? Math.Sqrt( a*a - b*b ) : - Math.Sqrt ( b*b - a*a));
Func<double, double> growth = (x) => x*2;
Func<double, double, double> comp = (x,y) => growth (f(x,y));
このパラダイムの背後にある考えは、純粋な関数型プログラミングでは、操作の順序とは関係なく問題を表現するということです。理論的には、式を逐次的に評価するか、または 活用の並列化の可能性 の部分式を配布するかに関わらず、最も効率的な実行パスを見つけるのは実装の仕事です。
ところで、これが関数型プログラマが不変性を好み、副作用を嫌う理由です。可変性と副作用により、評価順序に関する制約と期待が生じます。
関数型プログラミングの精神を理解したい場合は、 OCaml や F# などの関数型プログラミング言語を試してみる価値があります。チュートリアルの後、C#に簡単に戻り、機能を向上させるために役立つ機能的なスタイルを使用できます。
構成可能性とは、コンポーネントを組み合わせることができ、その組み合わせをそのパーツの代わりに使用できることを意味します。
式は、他の式と組み合わせて新しい式を形成できるという意味で合成可能です。また、より複雑な式は、単純な式を使用できる場所であればどこでも使用できます。
さらに重要なのは、式または部分式を関数に抽出し、式の項の代わりに関数呼び出しを使用できることです。
しかし、これは常にステートメントに当てはまるわけではありません!次のステートメントを検討してください。
if (foo) {
return bar;
}
このフラグメントがコード内の複数の場所で発生しても、これを単純な関数呼び出しで置き換えることはできません。
次のようなコードがあるとします。
if (done) {return result}
// more calculation
if (done) {return result}
// some more calculation
if (done) {return result}
ここにはかなりの量の繰り返しがあります。たぶん、あなたはそれを分解したいでしょう。 if
およびreturn
ステートメントが参照透過的で構成可能な場合、次のように記述できます。
checkDone = if (done) {return result}
checkDone
// more calculation
checkDone
// some more calculation
checkDone
明らかにそれは機能しませんが、純粋に機能的なコードに固執する場合は、結果に影響を与えることなく、常にそのような置換を行うことができます。 Scala Cats のStateモナドのこの例は、その代替可能性の優れた例です。