柯里亚诺,C#中的函数柯里化

函数柯里化是函数式编程的一个特性,在函数式编程中,函数被认为是一等公民。可是C#作为一种混合语言(由于,可以在C#中使用函数式编程的原因,我个人认为它是一门hybrid语言)也可以通过delegate来描述一个函数,但如果我们调用时指定的参数个数不等于这个delegate的参数个数,则会在得到一个异常。
在进入讨论之前,我先定义一个函数:
static int Accumulation(int baseSum,int count) { for (int i = 1; i <= count; i++) { baseSum+= i; } return baseSum; }
这段代码地球人都看得懂,就是以sum为基数从1累加到count。那么使用函数式编程语言怎么来实现呢?这里我以F#为例,
let rec Accumulation sum count= match count>0 with |true-> func sum count-1 |false-> sum
看起来,这似乎没有什么困难,但对于F#来说,我们可以为函数Accumulation少指定一个参数而不会得到一个错误:
let curriedAccumulation=Accumulation(100)
调用Accumulation后返回了一个新的函数,这个函数就是柯里化函数。如果调用curriedAccumulation就只需传入一个参数,而它的基数则是baseSum=100。大家都知道在C#中这样调用时会发生一个编译时错误的,那么如何才能够避免这个问题呢?
回到第一段代码,假设我改写下这段代码,把baseSum设为100,然后使用一个AccumulationWrapper函数包裹这个函数的调用:
static int AccumulationWapper(int count) { return Accumulation(100, count); }
通过这种技巧,我们就可以在编译时做到柯里化了,可是这个并不是我的目的,我们需要的是运行时能够提供这样的一种行为。所幸的是C#3.0开始为我们提供了表达式树,可以使用它来动态的装配或者拆卸一个方法。
谈到这里似乎你已经能够说出该如何做的思路了,其实就是动态的构建一个AccumulationWapper函数。那么这个函数该如何构建呢?下面是我的思路:
1.找到尚未指定的Accumulation函数的参数paramsValue。
2.然后以paramsValue为参数装配一个(paramsValue)=>Accumulation(100,paramsValue)的表达式。
装配的函数其实就有点类似于AccumulationWapper函数了,不过这个是发生在运行时,因此,我们能动态的指定baseSum的值。
interface ICurrying { TResult Currying(Delegate function, params object[] paramsValue); } public class Curry:ICurrying { public TResult Currying(Delegate function, params object[] paramsValue) { ....... }
}
ICurrying接口定义了一个Curring方法,这个方法用来将Delegate类型转化为对应的柯里化类型TResult,下面来实现Curring方法:
public TResult Currying(Delegate function, params object[] paramsValue) { var FunctionType = function.GetType(); var functionMethod = ((Delegate)(object)function).Method; var parameters = functionMethod.GetParameters(); if (parameters.Length < paramsValue.Length) { throw new ArgumentOutOfRangeException("The paramsValues's length is too long."); } ...... }
首先我们的判断是否指定的参数个数超过了function的参数总数,如果超出了则会得到一个异常,完成了基本的赋值和检查操作后,就是构造新函数的代码了。我们需要在parameters中排除掉已经赋值的参数,并把它们保存到paramtersExp当中,然后将paramsValue设置为constExp,最后表达式主体是对function的调用,但在这个之前,需要检查function是否为静态方法。
public TResult Currying(Delegate function, params object[] paramsValue) { ...... var paramtersExp = parameters.Where((p, pos) => pos < paramsValue.Length ? false : true) .Select(p => Expression.Parameter(p.ParameterType, p.Name)) .Cast() .ToArray(); var constExp = paramsValue.Select(param => Expression.Constant(param)).Cast().ToArray(); var callee = Expression.Constant(((Delegate)(object)function).Target); //Static or Instance Method var bodyExp = callee.Value == null ? Expression.Call(functionMethod, constExp.Concat(paramtersExp)) : Expression.Call(callee, functionMethod, constExp.Concat(paramtersExp)); return (Expression.Lambda(bodyExp, Array.ConvertAll(paramtersExp, (exp) => (ParameterExpression)exp)).Compile()); /* * (param1,param2,param3)=> * { * Method(const1,const2,const3,param1,param2,param3); * } * * * */ }
这样完成后,就得到一个柯里化的函数了,然后我将它的调用放入到一个Delegate的扩展方法中:
public static class DelegateExtension { public static TResult Currying(this Delegate func, params object[] parameters) { return new Curry().Currying(func, parameters); }
}
下面来测试一下吧:
Func func = Accumulation; var funcCurryed=func.Currying>(100); Console.WriteLine(funcCurryed(100));//5150
大功告成,虽然没有F#那样看起来自然,你可以在Currying下载所有代码。
Tags:  excel中函数 柯里叶尔 柯里奥利 柯里佐夫 柯里亚诺

延伸阅读

最新评论

发表评论