関数のカリー化と部分的な使用

Curryingという用語を初めて聞いたとき、私はすぐにおいしいタイ料理とインド料理を想像しました。 驚いたことに、会話は素晴らしいスパイスに関するものではなく、n個の引数を取る関数を1つの引数を取る関数に変換し、n-1個の引数を取るカリー化された関数を返すことであることがわかりました。 どこで役立つのでしょうか?



理論的な観点からは、これは興味深いです。 ラムダ計算では、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の値をどのように受け入れるかに注目してください。 残りの引数を取り、すべての引数を元の関数に適用する関数を返します。 これは、部分的に適用された関数を生成する一連の関数に一般化できます。



All Articles