理論的な観点からは、これは興味深いです。 ラムダ計算では、1つの引数を持つ関数を使用して操作する方が簡単だからです。 実用的な面では、これにより、プログラマーは、最初のk個の引数を修正することにより、基本関数から関数のファミリーを作成できます。 これは、壁に何かを固定する方法に似ており、このために2つのスタッドが必要です。 単一のヘアピンを貼り付けるまで、オブジェクトは表面のどこでも自由に移動できます。 ただし、1本のヘアピンで固定するとすぐに動きが制限されます。 そして最後に、2番目のヘアピンがこのオブジェクトに固定されると、オブジェクトには移動の自由がなくなります。 同様に、プログラマーが2つの引数の関数をカリー化し、それを最初の引数に適用すると、機能は1次元に制限されます。 最後に、新しい関数を2番目の引数に適用すると、最終的な値が計算されます。
C#では、これは、Func <A、B、R>型のデリゲート(それぞれA型とB型の2つの引数を持ち、R型を返すデリゲート)がある場合、Func <A、Func型を持つデリゲートを作成できることを意味します<B、R >>。 カリー化されたデリゲートは1つの引数のみを受け入れますが、元の関数の2番目の引数を受け入れ、最終的に値を返すデリゲートを返すことに注意してください。
例として追加関数を使用して、このような関数を作成することを検討してください。
Func < int , int , int > add = (x,y) => x + y;
カレー関数を適用して追加することにより、カレーを追加できます。
Func < int , Func < int , int >>curriedAdd = add.Curry();
実際、このカリー化された追加関数は、nに追加する関数を作成します。ここで、nはカリー化された追加関数の引数です。 たとえば、カリー化されたadd関数を値1に適用することにより、インクリメント関数を作成できます。
Func < int , int > inc = curriedAdd(1);
呼び出されたインクリメンタル関数は、1つとその引数の値を返します。 これで、さまざまな形式の加算に関数を使用できます。
Console .WriteLine(add(3,4)); // 7 <br>
Console .WriteLine(curriedAdd(3)(5)); // 8 <br>
Console .WriteLine(inc(2)); // 3
それでは、このカレー関数はどのように見えるのでしょうか? 実際、非常に簡単です。
public static Func <A, Func <B, R>> Curry<A, B, R>( this Func <A, B, R> f)<br/>
{<br/>
return a => b => f(a, b);<br/>
}
2つの引数の関数を取り、最初の引数をキャプチャしてから2番目の引数をキャプチャするラムダを返します。 両方の引数が提供されると、元の関数を引数で呼び出します。 このパターンに従って、異なるアリティを持つ関数をカリー化するカリー関数を作成するのは非常に簡単です。
これらの各関数を作成すると何が起こるか見てみましょう。 最初に、次のようなadd関数を作成しました。
(x, y) => x + y
addをカリー化した後、関数は次の形式を取ります。
x => y => x + y
値1でcurried addを呼び出してincを作成しました。これにより、基本的に次のような関数が作成されます。
y => 1 + y
関数をカリー化してから元の関数の最初のn個の引数を修正するという考え方は、関数の部分適用と呼ばれる概念に一般化できます。 たとえば、前の例のadd関数を使用すると、最初にcurriedAddを作成せずに、addから直接増分を作成できます。
Func < int , int > inc = add.Partial(1);
部分関数は次のように記述されます。
public static Func <B, R> Partial<A, B, R>(this
Func <A, B, R> f, A a)<br>
{<br>
return b => f(a, b);<br>
}
関数が、関数と関数の最初の引数と同じ型を持つaの値をどのように受け入れるかに注目してください。 残りの引数を取り、すべての引数を元の関数に適用する関数を返します。 これは、部分的に適用された関数を生成する一連の関数に一般化できます。