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->
let sumtemp=sum+count
let counttemp=count-1
Accumulation sumtemp counttemp
|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<out TResult>
{
TResult Currying
<TResult>(Delegate function, params object[] paramsValue);
}

public class Curry<TResult>:ICurrying<TResult>
{


public TResult Currying<TResult>(Delegate function, params object[] paramsValue)
{
.......
}
}

   ICurrying接口定义了一个Curring<TResult>方法,这个方法用来将Delegate类型转化为对应的柯里化类型TResult,下面来实现Curring方法:

 public TResult Currying<TResult>(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<TResult>(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
<Expression>()
.ToArray();
var constExp
= paramsValue.Select(param => Expression.Constant(param)).Cast<Expression>().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<TResult>(bodyExp, Array.ConvertAll(paramtersExp, (exp) => (ParameterExpression)exp)).Complie()
}

   这样完成后,就得到一个柯里化的函数了,然后我将它的调用放入到一个Delegate的扩展方法中:

public static class DelegateExtension
{
public static TResult Currying<TResult>(this Delegate func, params object[] parameters)
{
return new Curry<TResult>().Currying<TResult>(func, parameters);
}
}

  下面来测试一下吧:

 Func<int, int, int> func = Accumulation;
var funcCurryed
=func.Currying<Func<int, int>>(100);
Console.WriteLine(funcCurryed(
100));//5150

  大功告成,虽然没有F#那样看起来自然,你可以在Currying下载所有代码。

  

转载于:https://www.cnblogs.com/GardeniaPeter/archive/2011/07/16/Currying.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值