函数式设计的核心与函数的应用以及函数如何作为算法的基本模块有关。利用局部套用技术可以把所有函数看成是函数类的成员,这些函数只有一个形参,有了局部套用,才有部分应用。部分应用是使函数模块化成为可能的两个技术之一,另一个是组合。
C#函数式程序设计之参数的解耦
局部套用是一种转换技术,它把一个需要接收多个参数的函数转换为一系列函数,后者每次只接受一个参数并返回序列中的下一个函数。在这个函数链的末尾,所有的参数都可以使用,并允许原算法执行自己的操作。
考虑下面这个简单的函数,它使用了与C#2.0兼容的匿名方法定义语法:
Funcadd = delegate(int x, int y) { return x + y; };
这个加法函数接受两个参数后返回两个数的相加结果,调用这个函数时,调用程序必须同时提供两个参数,C#语法约束不允许任何其他方法。
要把局部套用技术应用于这个函数,意味着要创建一个一次只能接受一个参数且返回下一个函数的函数,即如下:
Func> add = delegate(int x) { return delegate(int y) { return x + y; }; };
局部套用的基本原理也可以应用于用Lambda语法定义的函数:
FuncaddTest = (x, y) => x + y;
使用局部套用后的函数如下:
Func> addTest = x => (y => x + y);
现在把局部套用技术应用于任意个参数上,假设有一个函数有以下的参数列表:
Func<...> f = (part1, part2, part3, ..., partX) => ...;
局部套用后的函数签名格式如下:
Func<...> cf = part1 => part2 => part3 => ... => partX => ...;
Lambda表达式主体部分代码,即最后一个=> 运算符的右侧部分,在转换过程中保持不变。
类型排列形式的变化遵从同样的规则,考虑如下的泛型委托类型:
Func
应用局部套用后得到如下的类型排列形式:
Func>>>
有趣的是,如果显式提供Lambda表达式的类型或者C#2.0匿名方法的类型,则采用类型推断也可以推断出泛型参数,这令人感到奇怪,因为在同样的情形下,无法用var关键字把函数保存到一个变量中:
// this is invalidvar mult = (int x, int y) => x * y;
对于内联的Lambda表达式无法调用扩展方法。
局部套用技术也可以应用于C#类声明的比较常规的方法。静态方法和实例方法都可以应用局部套用技术。此外,类使得局部套用函数的外部调用成为可能。
C#函数式程序设计之调用函数的各部分
使用局部套用格式函数的最主要理由是只需要部分计算参数而不是全部参数就可以调用这个函数。这个过程就是所谓的部分应用,部分应用是一种函数构建技术。
考虑下面这段代码,它使用一个从零开始采用局部套用格式的函数:
Func> add = x => y => x + y;var add5 = add(5);
这里调用了局部套用函数add,但是只给它传递一个参数,显然, 在这种情况下无法进行加法计算,因为它需要两个参数,用一个参数调用将返回一个新函数。我们不是立即调用这个新函数,而是把它保存在变量中供以后使用。
调用这个新创建的函数可以像调用其他函数那样:
int result = add5(37);
通常,部分应用接受一个本质属于泛型的函数,创建一个毕竟专用的新函数。
C#函数式程序设计之参数顺序的重要性
当应用局部套用技术和部分应用技术时,说明参数顺序的重要性有一个简单的理由:函数的部分应用只能从参数列表的起始位置开始。在决定参数的顺序时,最重要的是考虑函数中哪个参数最有可能使用部分应用。