这个系列涉及到了F#这门语言,也许有的人觉得这样的语言遥不可及,的确我几乎花了2-3年的时间去了解他;也许有人觉得学习这样的冷门语言没有必要,我也赞同,那么我为什么要花时间去学习呢?作为一门在Tiobe排行榜里50名开外的语言,很显然我学习他并不是为了混口饭吃,也许有一天我会为了养家糊口转向Java,但在这之前Java并没有任何吸引我学习他的地方。因为从本质上说C#和Java都是同种风格的语言,他两在语言层面的能力几乎是一样的。
如果一种编程语言不能影响你对编程的思考方式,就不值得学习
来自 《Epigrams on Programming》
函数式语言来自数学家之手,天生有点高不可攀,各种稀奇古的名词也会让人望而生畏,但他不一定就比OO范式复杂。我们从一开始接触计算机语言几乎就在学习OO,一般来说没有3-5年的编程经验几乎不会领会OO那一套思想,各种设计模式和原则对于一个经验5年的OO选手都不一定能完全领会。从这个角度讲不见得OO就是比函数式编程思想更加简单的范式。
终于可以引出本文的重头戏Currying,Currying一词源于以为对函数式编程语言有影响的数学家Haskell Curry。Currying讲了一件什么样的事情呢?
在函数式语言中并没有类的概念,很显然类是一个来自OO的概念。函数式编程思想中只有函数,那么一个应用程序全靠函数来堆积现实么?OO通过类的概念封装了细节,通过向外界提供更高层级的抽象来防止程序陷入细节。函数式语言则通过组合函数来提供更高级,更复杂的功能。
大家一定见到过lego积木吧,不小小看lego每一个小的零件,通过组合,你可以堆出一个超乎想象的庞然大物。
整个函数式编程的核心思想就是组合,他描述了如何把各种形形色色的东西例如函数、各种类型组合在一起。
那么问题来了,是不是所有的函数都可以自由组合呢?答案是否定的。以一个对数字的操作为例:
整个过程可以组合为一个函数:
只有函数在拥有一个输入和一个输出的时候才能被组合起来,那么对于拥有两个参数的函数如何组合?答案就是Currying。
下面的函数接受两个参数: public int AddWithTwoParameters(int x, int y) { return x + y; }
如何把它变成只接受一个参数的函数?
public Func<int, int> AddWithTwoParameters(int x) { Func<int, int> subFunction = y => x + y; return subFunction; }经过变形,当你调用AddWithTwoParameters时:
调用AddWithTwoParameters并传入一个参数
AddWithTwoParameters返回了一个新的函数,并把参数x包裹在了里面
通过传递第二个参数来调用新生成的函数
对比下面这两种函数的使用方式:
如果用C#中的Func来表示这两种风格的函数,他们会有一点微妙的区别:
普通版本的Add函数可以用下面的Func来表示:
Func<int,int,int>他表示一个接受两个int值的函数,返回类型为int。
Curry版本的Add函数可以用下面的Func来表示:
Func<int,Func<int,int>>他表示一个接受int参数并返回类型为Func<int,int>的函数。
Currying多个参数的函数跟两个函数的Currying过程类似,考虑三个参数的函数,正常版本如下:
public int AddWithThreeParameters(int x, int y, int z) { return x + y + z; }Currying版本:
public Func<int, Func<int, int>> AddWithThreeParameters(int x) { Func<int, Func<int, int>> subFunction = y => z => x + y + z; return subFunction; }调用过程:
// 正常版本的调用 var result = AddWithThreeParameters(1, 2, 3); // Curried的版本调用 var intermediateFn1 = AddWithThreeParameters(1); var intermediateFn2 = intermediateFn1(2); var result1 = intermediateFn2(3);你能够写出四个参数的Currying版本吗?当然可以,同时你也许已经观察到了,Currying版本的函数返回值类型特别繁琐,如果你第一次看到这样的返回类型一定不明白他到底在干嘛,另外手动Currying的过程也很痛苦,能不能自动完成Currying呢?
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func) { return x => y => func(x, y); } public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3,TResult> func) { return x => y => z=>func(x, y,z); } F#如何做Currying