カリー化の実用について考えてみたいと思います。
まずは、C# でカリー化を表す関数を、拡張メソッドを使って以下のように実装していきます。
カリー化のほか、非カリー化および部分適用のための関数も実装しておきます。
引数の数ごとにメソッドを定義しなければなりませんが、ここではとりあえず T3 までにしておきます。
public static class FunctionHelper
{
// カリー化
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
{
return p1 => p2 => func(p1, p2);
}
public static Func<T1, Func<T2, T3, TResult>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func)
{
return p1 => (p2, p3) => func(p1, p2, p3);
}
// 非カリー化
public static Func<T1, T2, TResult> Uncurry<T1, T2, TResult>(this Func<T1, Func<T2, TResult>> func)
{
return (p1, p2) => func(p1)(p2);
}
public static Func<T1, T2, T3, TResult> Uncurry<T1, T2, T3, TResult>(this Func<T1, Func<T2, T3, TResult>> func)
{
return (p1, p2, p3) => func(p1)(p2, p3);
}
// 部分適用
public static Func<T2, TResult> Partial<T1, T2, TResult>(this Func<T1, T2, TResult> func, T1 arg1)
{
return func.Curry()(arg1);
}
public static Func<T2, T3, TResult> Partial<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, T1 arg1)
{
return func.Curry()(arg1);
}
}
さて、次のようなコレクション操作をしたいと思います。
- 奇数の二乗を先頭から 10 個取得する
- 処理結果は文字列として比較したときの順序で並べる
- 処理結果は整数の配列型とする
期待結果は [1, 121, 169, 225, 25, 289, 361, 49, 81, 9] という配列ですが、LINQ to Objects で次のように書けます。
static void Main()
{
var result = Enumerable.Range(1, int.MaxValue)
.Where(i => i % 2 == 1)
.Select(i => i * i)
.Take(10)
.OrderBy(Convert.ToString)
.ToArray();
}
もしこの他に、「偶数」「3 で割り切れる」のようなロジックをどこかで実装しなければならないとしたらどうでしょう。
今回はラムダ式で書けてしまう程度の単純な例なのであまり利点が感じられないようにも見えますが、
関数自体をパラメーター化することで関心事を分離できます。
つまり、関数の本質に相当する部分 (上の例では、「余りが等しい」のみを表す部分) をグローバルに用意しておき、
パラメーターを与えることで具体的に必要な関数を作成する方法が考えられます。
部分適用関数 (Partial メソッド) を利用すれば、次のように実装できます。
/// <summary>指定された剰余を持つかどうかを判定します。</summary>
static readonly Func<int, int, int, bool> Mod = (remainder, divider, value) => value % divider == remainder;
/// <summary>累乗を求めます。</summary>
static readonly Func<int, int, int> Power = (index, value) => (int)Math.Pow(value, index);static void Main()
{
var result = Enumerable.Range(1, int.MaxValue)
.Where(Mod.Partial(1).Partial(2))
.Select(Power.Partial(2))
.Take(10)
.OrderBy(Convert.ToString)
.ToArray();
}
このように、LINQ to Objects のメソッドの引数に渡す関数を作成する手段として、カリー化を応用できます。
バージョン情報
C# 3.0 以降
.NET Framework 3.5 以降