拿 C# 搞函数式编程 - 1
最近闲下来了,准备出一个 c# 搞 fp 的合集。本合集所有代码均以 c# 8 为示例。
可能你说,为什么要这么做呢?回答:为了好玩。另外,意义党们请 gun cu ke!
c# 有委托,而且有 func<> 和 action<>,可以说函数被视为一等功名,跟 int、bool 等类型并没有什么区别。那么很多事情就简单了。
纯函数
什么是纯函数呢?纯函数就是 f(x),它们接收参数,得到结果,并且相同的参数得到的结果一定是相同的,用映射来说,它是满射的。另外这个函数不会改变任何的状态值,它是无副作用的。
柯里化
首先,有一个东西让我觉得不爽,那就是一般来说 c# 里的函数调用不是柯里化的,这也就意味着我没法一个一个传参数进去,也没法把传了一部分参数的调用作为一个新函数拿去给别的地方用,那要怎么办呢?
自己动手,丰衣足食!
一个标准的加法函数可以这么写:
var function = new func<int, int, int> ((x, y) => x + y); function(1, 2); // returns 3
如果我们想以柯里化形式调用的话,理想状态是这么个样子的:
function 1 2
但是这个括号我们是省不了的,所以这样也是可以接受的:
function(1)(2);
我们看一下这个调用形式,不就是 func<int, func<int, int>> 嘛!so easy~
我们只需要把 func<int, int, int> 转化为 func<int, func<int, int>>:
func<int, func<int, int>> currying(func<int, int, int> f) => x => y => f(x, y);
这样写就 ok 啦。进一步改造成扩展方法:
public static class curryingextensions { public static func<int, func<int, int>> currying(this func<int, int, int> f) => x => y => f(x, y); }
于是我们只需要:
var function = new func<int, int, int> ((x, y) => x + y) .currying(); function(1)(2); // returns 3
就可以采用柯里化形式调用该函数啦。
进一步我们用泛型改造,让柯里化适用于任何类型:
public static class curryingextensions { public static func<t1, func<t2, toutput>> currying<t1, t2, toutput>(this func<t1, t2, touput> f) => x => y => f(x, y); }
如果遇到更多参数,我们只需要给这个静态类里面再加一个扩展方法即可。
那 action<> 呢?这个东西在我看来完全就是副作用,具体下方有讲,我们不用他(逃
unit
什么是 unit 呢?unit 就是任何函数调用后如果没有结果,就会返回的一个东西。
可能你说,void 不就可以了?
但是如果一个纯函数,它没有返回值(即 action<>),意味着这个函数它有输入没输出,那这个函数除了能用来产生副作用之外,就什么都干不了了。这不清真!
因此我们需要一个 unit 来代替 void,偷个懒,这个 unit 就用 ulong 来代替吧。
高阶函数
什么叫做高阶函数,把函数当作参数传给另一个函数,接收这个函数参数的函数就叫做高阶函数。
举个例子:f(g(x)),f 即高阶函数。
假设我们现在要开一个超市,超市有很多的产品,每种产品价格不同,不同产品可能还有各自的折扣。我们有很多种快乐水,每种快乐水价格不一样,可口快乐水 3.5 块,百事快乐水 3 块,麦当劳快乐水 9 块,快乐水价格计算函数:
var happywater = new func<float, int, float> ((float price, int number) => number * price) .currying(); // 调用:happywater(快乐水单价)(快乐水件数); var cocahappywater = happywater(3.5f); var pepsihappywater = happywater(3); var mcdhappywater = happywater(9);
超市可能有折扣,a 超市不打折,b 超市打八折,计算价格函数:
var calcprice = new func<func<int, float>, float, int, float> ((calc, discount, number) => discount * calc(number)) .currying(); // 调用:calcprice(快乐水价格计算函数)(超市折扣)(快乐水件数);
现在我们分别在 a 超市买百事快乐水、b 超市买可口快乐水,麦当劳的太贵了我们不买,价格计算函数为:
var pepsipricecalc = calcprice(pepsihappywater); var cocapricecalc = calcprice(cocahappywater); var pricecalca = pepsipricecalc(1); // a 超市 var pricecalcb = cocapricecalc(0.8f); // b 超市
最后我们在 a 超市买了 3 瓶百事快乐水,b 超市买了 5 瓶可口快乐水,计算总价:
var pricea = pricecalca(3); var priceb = pricecalcb(5); var total = pricea + priceb;
最后得到 total = 23 元。
可以看到这些函数都是可拆卸并且可以随意组合的,而且满足 f(g(x)) = g(f(x))。
贴上完整代码示例:
using system; namespace colamarket { static class curryingextensions { public static func<t1, func<t2, toutput>> currying<t1, t2, toutput>(this func<t1, t2, toutput> f) => x => y => f(x, y); public static func<t1, func<t2, func<t3, toutput>>> currying<t1, t2, t3, toutput>(this func<t1, t2, t3, toutput> f) => x => y => z => f(x, y, z); } class program { static void main(string[] args) { var happywater = new func<float, int, float> ((float price, int number) => number * price) .currying(); var cocahappywater = happywater(3.5f); var pepsihappywater = happywater(3); var mcdhappywater = happywater(9); var calcprice = new func<func<int, float>, float, int, float> ((calc, discount, number) => discount * calc(number)) .currying(); var pepsipricecalc = calcprice(pepsihappywater); var cocapricecalc = calcprice(cocahappywater); var pricecalca = pepsipricecalc(1); var pricecalcb = cocapricecalc(0.8f); var pricea = pricecalca(3); var priceb = pricecalcb(5); var total = pricea + priceb; console.writeline(total); } } }
下一篇将会讲更多的东西,如 functor、applicative 和 monad 等等。