f: AxB => C
f: (a,b) -> a + b
Сложение взято для примера. Мы можем её преобразовать следующим образом:
Curry(f): A => (B => C )
Curry(f): a -> (b ->a + c)
То есть теперь каждому числу Curry(f) сопоставляет функцию одной переменной. При этом выполняется равенство
f(a,b) = Curry(f)(a)(b)
Кроме того, при помощи функции каррирования мы можем фиксировать значение первого аргумента функции f.
AddOne = Curry( + )(1)
PowerOf2 = Curry( ^ )(2)
Теперь можно использовать новополученные функции. Например:
AddOne(5) = 6
PowerOf2(3) = 8
Подобные функции дают некоторую гибкость, и могут быть использованы, например при использовании методов Linq.
К сожалению, в третьей версии c# нельзя использовать вот такие конструкции:
internal static class Program
{
public static int AddFunction(int a, int b)
{
return a + b;
}
private static void Main()
{
var addTwo = FunctionalTools.Curry(AddFunction)(2)
var addTwoExtention = AddFunction.Curry()(2)
}
}
Поскольку AddFunction на самом деле является "Method Group", и особенно в случае если Curry перегружен, не может правильно определить сигнатуру метода. Поэтому придётся использовать ручное приведение типов. Вот так: internal static class Program
{
public static int AddFunction(int a, int b)
{
return a + b;
}
private static void Main()
{
var addTwo = FunctionalTools.Curry((Func<int, int, int>)AddFunction)(2)
var addTwoExtention = ((Func<int, int, int>)AddFunction).Curry()(2)
}
}
Практически всё уже сказано, осталось только реализовать функционал. К сожалению, сделать самый общий тип мы не сможем, но мы можем сделать покрытие значительной части функционала. Скажи, вы часто используете методы, которые содержат более, чем 8 параметров? Думаю, довольно редко. Поэтому вначале обьявим несколько делегатов.
public delegate TResult Func<T1, T2, T3, T4, T5, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
public delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
Но обьявлять таким образом кучу делегатов скучно, долго, неинтересно и не информативно. Более того, если попытки написать функцию Curry для каждого возможного варианта не вызывают никакой радости.
Поэтому рекомендую посмотреть в сторону Text Template Transformation Toolkit, про него на хабре уже писали.
Немножечко перепишем объявление делегатов:
<# for(var index = Shapr3FuncCount; index < MaxParametersCount; index++) { #>
public delegate TResult Func<<#= BuildTemplate(1,index) #>, TResult>(<#= BuildTemplate(1,index,x=>TypeNamePrefix+x+ArgPrefix+x) #>);
<#} #>
При этом были использованы следующие константы и функции:
private const int Shapr3FuncCount = 5;
private const int MaxParametersCount = 9;
private const string TypeNamePrefix = "T";
private const string ArgPrefix = " arg";
private string BuildTemplate(int a,int b,Func<int,string> func){
return string.Join(", ",System.Linq.Enumerable.Range(a, b).Select(func).ToArray());
}
private string BuildTemplate(int a,int b){
return BuildTemplate(a, b, x => TypeNamePrefix + x);
}
private string BuildArgs(int howMany){
return BuildTemplate(0,howMany,x => ((char)('b'+x)).ToString());
}
Вот мы сгенерировали недостающие делегаты, теперь можно смело приступить непосредственно к написанию функций каррирования.Всё действие будет происходить внутри конструкции:
<# for(var index = MinParametersCount; index < MaxParametersCount; index++) { #>
Собственно, ничего сложного в написании функции нет. Вот она:public static Func<T0, Func<<#= BuildTemplate(1,index-1) #>>> Curry<<#= BuildTemplate(0,index) #>>(this Func<<#= BuildTemplate(0,index) #>> func)
{
return a => (<#= BuildArgs(index - 2) #>) => func(a<#= index>MinParametersCount?", ":"" #><#= BuildArgs(index - 2) #>);
}
А к ней я решил добавить ещё одну функцию, которая позволит сразу зафиксировать первый аргумент какой-либо функции. Мне кажется, что это является одной из наиболее востребованных возможностей каррирования.
public static Func<<#= BuildTemplate(1,index-1) #>> Bind<<#= BuildTemplate(0,index) #>>(this Func<<#= BuildTemplate(0,index) #>> func, T0 value)
{
return Curry(func)(value);
}
Теперь осталось только сохранится и посмотреть получивший файл. Изменяя константы можно плодить дополнительные функции, которые удовлетворит любые потребности в аргументах, хотя и сомневаюсь, что такие могут реально возникнуть.
Для удобства можно реализовать ещё несколько функций высших порядков. Я приведу лишь одну, которая позволит менять местами аргументы функции:
public static Func Flip<T1,T2,TResult>(this Func<T1,T2,TResult> func)
{
return (x,y) => func(y,x);
}
За этим рассказ окончен, хочу лишь ещё раз обратить внимание что различные функциональные практики постепенно входят в жизнь обыкновенного программиста, что немного радует.
Шаблон и получившийся в результате кодогенерации файл можно скачать.