最近闲下来了,准备出一个 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(, ); // returns 3

如果我们想以柯里化形式调用的话,理想状态是这么个样子的:

function  

但是这个括号我们是省不了的,所以这样也是可以接受的:

function()();

我们看一下这个调用形式,不就是 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()(); // 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();
var mcdHappyWater = happyWater();

超市可能有折扣,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(); // A 超市
var priceCalcB = cocaPriceCalc(0.8f); // B 超市

最后我们在 A 超市买了 3 瓶百事快乐水,B 超市买了 5 瓶可口快乐水,计算总价:

var priceA = priceCalcA();
var priceB = priceCalcB();
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();
var mcdHappyWater = happyWater(); 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();
var priceCalcB = cocaPriceCalc(0.8f); var priceA = priceCalcA();
var priceB = priceCalcB();
var total = priceA + priceB; Console.WriteLine(total);
}
}
}

下一篇将会讲更多的东西,如 Functor、Applicative 和 Monad 等等。

拿 C# 搞函数式编程 - 1的更多相关文章

  1. 拿 C# 搞函数式编程 - 3

    前言 今天和某个人聊天聊到了 C# 的 LINQ,发现我认识的 LINQ 似乎和大多数人认识的 LINQ 不太一样,怎么个不一样法呢?其实 LINQ 也可以用来搞函数式编程. 当然,并不是说写几个 l ...

  2. 拿 C# 搞函数式编程 - 2

    前一阵子在写 CPU,导致一直没有什么时间去做其他的事情,现在好不容易做完闲下来了,我又可以水文章了哈哈哈哈哈.顺便成立了自己的专栏:hez2010 的编程日常,欢迎大家关注(逃 有关 FP 的类型部 ...

  3. Guava 是个风火轮之函数式编程(3)——表处理

    云栖社区> 博客列表> 正文 Guava 是个风火轮之函数式编程(3)--表处理 潘家邦 2016-01-26 13:19:21 浏览1062 评论0 java Guava 摘要: 早先学 ...

  4. (转) 站在C#和JS的角度细谈函数式编程与闭包

    1.函数式编程是什么? 摘自百度的说法是.函数式编程是种编程典范,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 λ 演算(lambda calculus).而且λ演算的函数可以接受函数当作输 ...

  5. (转)现代C++函数式编程

    本文转自:http://geek.csdn.net/news/detail/96636     现代C++函数式编程 C++ 函数式编程 pipeline 开发经验 柯里化 阅读2127    作者简 ...

  6. Python修饰器的函数式编程

    Python的修饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都 ...

  7. 测试和恢复性的争论:面向对象vs.函数式编程

    Michael Feathers最近的博文在博客社区引发了一场异常激烈的论战.Feathers发表言论说一些面向对象编程语言的内嵌特性有助于测试的进行,并且使用面向对象编程语言编写的代码更容易恢复. ...

  8. boost 的函数式编程库 Phoenix入门学习

    这篇文章是我学习boost phoenix的总结. 序言 Phoenix是一个C++的函数式编程(function programming)库.Phoenix的函数式编程是构建在函数对象上的.因此,了 ...

  9. 函数式编程很难,这正是你要学习它的原因 | 外刊IT评论网

    函数式编程很难,这正是你要学习它的原因 | 外刊IT评论网 函数式编程很难,这正是你要学习它的原因 156 次分享 新浪微博 腾讯微博 Tweet 人人网 QQ空间 很奇怪不是,很少有人每天都使用函数 ...

随机推荐

  1. 自定义SWT控件一之自定义单选下拉框

    一.自定义下拉控件 自定义的下拉框,是自定义样式的,其中的下拉框使用的是独立的window,非复选框的下拉框双击单机其它区域或选择完之后,独立window构成的下拉框会自动消失. package co ...

  2. DesignPattern系列__04里氏替换原则

    1.内容引入--继承体系的思考 在继承中,凡是在父类已经实现的方法,其实算是一种契约或者规范,子类不应该在进行更改(重写):但是,由于这一点不是强制要求,所以当子类进行重写的时候,就会对继承体系产生破 ...

  3. 树莓派 + Windows IoT Core 搭建环境监控系统

    前言:Windows IoT 是微软为嵌入式开发板设计的一种物联网操作系统,运行Windows UWP(C# 开发),可以设计出丰富的交互界面,驱动GPIO,连接一些传感器做有意思的事,本文详细介绍如 ...

  4. JS中以一个方法作为参数的写法

    一:以方法作为参数 这下来说直接以一个方法来作为参数的写法,直接上代码: -----------这样调用的方法------------- go(function(){ alert("succ ...

  5. oracle 断电启动失败:ORA-00600: internal error code, arguments

    转载地址: http://www.2cto.com/database/201312/261602.html 由于服务器断电,启动 oracle 时报 ORA-00600 错误 查看 oracle tr ...

  6. Spring Security (CORS)跨域资源访问配置

    1.CORS介绍 CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing).它允许浏览器向跨源(协议 + 域名 + 端口)服务 ...

  7. Redis简单梳理及集群配置

    **REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵 ...

  8. Spring Boot 中的同一个 Bug,竟然把我坑了两次!

    真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...

  9. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  10. net core Webapi基础工程搭建(四)——日志功能log4net

    目录 前言 log4net 依然是,NuGet引用第三方类库 整合LogUtil 小结 前言 一个完整的项目工程离不开日志文件的记录,而记录文件的方法也有很多,可以自己通过Stream去实现文件的读写 ...