最近闲下来了, 准备出一个 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 等等.
来源: https://www.cnblogs.com/hez2010/p/11487006.html