C# 函数式编程及Monads.net库
函数式编程中,一切皆为函数,这个函数一般不是类级别的,其可以保存在变量中,可以当做参数或返回值,是函数级别的抽象和重用,将函数作为可重用的基本模块,就像面向对象中一切皆为对象,把所有事物抽象为类,面向对象编程通过继承和组合来实现类或模块重用,而函数式编程通过局部套用来实现函数重用;两种编程模式相辅相成,各有侧重点。函数式编程涉及高阶函数,纯函数、引用透明、闭包、局部套用、部分应用、惰性求值、单子等概念。
C#不是函数式程序设计语言,但是随着委托、lambda表达式、扩展方法、Linq、并行库的引入,不断方便我们进行函数式程序设计,另外,Monads.net库也方便我们进行函数式编程。
一、函数式编程基本概念
1、高阶函数
以函数为参数或返回结果的函数,如一个排序函数,其能适用于各种类型的数据,其排序逻辑一样,但是不同数据类型的值比较方法不一样,把比较函数当做参数,传递给排序函数。另外,C# 中Enumerable类中的Where、Select、SelectMany、First扩展方法都是高阶函数。
2、引用透明/纯函数
一个函数返回值,只取决于传递给它的参数,程序状态通常不会影响函数返回值,这样的函数称为纯函数,其没有副作用,副作用即多个方法或函数共享访问同一数据,函数式程序设计的主要思想之一就是控制这样的副作用。
3、变量不变性
变量分局部变量(方法或类实例的局部变量) 全局变量(类的静态字段);变量是可变的,函数式程序设计并不欢迎程序中可变值的想法,变量值越公开带来的问题越严重,一般原则是变量的值最好保持不变或在最小的作用域内保存其值,纯函数最好只使用在自己模块中定义的变量值,不访问其作用域之外的任何变量。
4、闭包
当函数可以当成参数和返回值在函数之间传递时,编译器利用闭包扩展变量的作用域,以保证随时能得到所需要数据;局部套用(currying或加里化)和部分应用依赖于闭包。
static Func<int, int> GetClosureFunction()
{
//局部变量
int val = 10;
//局部函数
Func<int, int> internalAdd = x => x + val;
Console.WriteLine(internalAdd(10));//输出20
val = 30;
//局部变量的改变会影响局部函数的值,即使变量的改变在局部函数创建之后。
Console.WriteLine(internalAdd(10));//输出40
return internalAdd;
}
static void Closoures()
{
Console.WriteLine(GetClosureFunction()(30));//输出60
}
局部变量val 作用域应该只在GetClosureFunction函数中,局部函数引用了外层作用域的变量val,编译器为其创建一个匿名类,并把局部变量当成其中一个字段,并在GetClosureFunction函数中实例化它,变量的值保存在字段内,并在其作用域范围外继续使用。
5、局部套用或函数柯里化
函数柯里化是一种使用单参数函数来实现多参数函数的方法
多参函数:
Func<int, int, int> add = (x, y) => x + y;
单参数函数:
Func<int, Func<int, int>> curriedAdd = x => (y => x + y);
调用:curriedAdd (5)(3)
应用场景:预计算,记住前边计算的值避免重复计算
static bool IsInListDumb<T>(IEnumerable<T> list, T item)
{
var hashSet = new HashSet<T>(list);
return hashSet.Contains(item);
}
调用:
IsInListDumb(strings, "aa");
IsInListDumb(strings, "aa");
改造后:
static Func<T, bool> CurriedIsInListDumb<T>(IEnumerable<T> list)
{
var hashSet = new HashSet<T>(list);
return item => hashSet.Contains(item);
}
调用:
var curriedIsInListDumb = CurriedIsInListDumb(strings);
curriedIsInListDumb("aa");
curriedIsInListDumb ("bb");
6、部分应用或偏函数应用
找一个函数,固定其中的几个参数值,从而得到一个新的函数;通过局部套用实现。
static void LogMsg(string range, string message)
{
Console.WriteLine($"{range} {message}");
}
//固化range参数
static Action<string> PartialLogMsg(Action<string, string> logMsg, string range)
{
return msg => logMsg(range, msg);
}
static void Main(string[] args)
{
PartialLogMsg(LogMsg, "Error")("充值失败");
PartialLogMsg(LogMsg, "Warning")("金额错误");
}
部分应用例子:
代码重复版本:
using(var trans = conn.BeginTransaction()){
ExecuteSql(trans, "insert into people(id, name)value(1, 'Harry')");
ExecuteSql(trans, "insert into people(id, name)value(2, 'Jane')");
...
trans.Commit();
}
优化1:函数级别模块化
using(var trans = conn.BeginTransaction()){
Action<SqlCeTransaction, int, string> exec = (transaction, id, name) =>
ExecuteSql(transaction, String.Format(
"insert into people(id, name)value({0},'{1}'", id, name));
exec (trans, 1, 'Harry');
exec (trans, 2, 'Jane');
...
trans.Commit();
}
优化2:部分应用
using(var trans = conn.BeginTransaction()){
Func<SqlCeTransaction, Func<int, Action<string>> exec = transaction => id => name =>
ExecuteSql(transaction, String.Format(
"insert into people(id, name)value({0},'{1}'", id, name)))(trans);
exec (1)( 'Harry');
exec (2)( 'Jane');
...
trans.Commit();
}
优化3:直接通过闭包简化
using(var trans = conn.BeginTransaction()){
Action<SqlCeTransaction, int, string> exec = ( id, name) =>
ExecuteSql(trans , String.Format(
"insert into people(id, name)value({0},'{1}'", id, name));
exec (1, 'Harry');
exec ( 2, 'Jane');
...
trans.Commit();
}
7、惰性求值/严格求值
表达式或表达式的一部分只有当真正需要它们的结果时才对它们求值,严格求值指表达式在传递给函数之前求值,惰性求值的优点是可以提高程序执行效率,复杂算法中很难决定某些操作执行还是不执行。
如下例子:
static int BigCalculation()
{
//big calculation
return 10;
}
static void DoSomething(int a, int b)
{
if(a != 0)
{
Console.WriteLine(b);
}
}
DoSomething(o, BigCalculation()) //严格求值
static HigherOrderDoSomething(Func<int> a, Func<int> b)
{
if(a() != 0)
{
Console.WriteLine(b());
}
}
HigherOrderDoSomething(() => 0, BigCalculation)//惰性求值
这也是函数式编程的一大好处。
8、单子(Monad)
把相关操作按某个特定类型链接起来。代码更易阅读,更简洁,更清晰。
Monads.net是GitHub上一个开源的C#项目,提供了许多扩展方法,以便能够在C#编程时编写函数式编程风格的代码。主要针对class、Nullable、IEnuerable以及Events类型提供
一些扩展方法。地址:https://github.com/sergeyzwezdin/monads.net。下面举些例子:
示例一: 使用With扩展方法获取某人工作单位的电话号码
var person = new Person();
var phoneNumber = "";
if(person != null && person.Work != null && person.Work.Phone != null)
{
phoneNumber = person.Work.Phone.Number;
}
在Monads.net中:
var person = new Person();
var phoneNumber = person.With(p => p.Work).With(w => w.Phone).With(p => p.Number);
代码中主要使用了With扩展方法, 源代码如下:
public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
where TSource : class
{
if ((object) source != (object) default (TSource))
return action(source);
return default (TResult);
}
person.With(p => p.Work)这段代码首先判断person是否为空,如果不为Null则调用p => p.Work返回Work属性,否则返回Null。
接下来With(w => w.Phone), 首先判断上一个函数返回值是否为Null,如果不为Null则调用w => w.Phone返回Phone属性,否则返回Null。
由此可以看出, 在上面的With函数调用链上任何一个With函数的source参数是Null,则结果也为Null, 这样不抛出NullReferenceException。
示例二: 使用Return扩展方法获取某人工作单位的电话号码
在示例一中,如果person,Work,Phone对象中任一个为Null值phoneNumber会被赋于Null值。如果在此场景中要求phoneNumber不能Null,而是设置一个默认值,应该怎么办?
var person = new Person();
var phoneNumber = person.With(p => p.Work).With(w => w.Phone).Return(p => p.Number, defaultValue:"11111111");
当调用Return方法的source参数为Null时被返回。
示例三: Recover
Person person = null;
//person = new Person();
if(null == person)
{
person = new Person();
}
在Monads.net中:
示例四: try/catch
Person person = null;
try
{
Console.WriteLine(person.Work);
}
catch(NullReferenceException ex)
{
Console.WriteLine(ex.message);
}
在Monads.net中:
Person person = null;
person.TryDo(p => Console.WriteLine(p.Work), typeof(NullReferenceException)).Catch(ex => Console.WriteLine(ex.Message));
//忽略异常
Person person=null;
try
{
Console.WriteLine(person.Work);
}
catch()
{
}
在Monads.net中:
person.TryDo(p=>Console.WriteLine(p.Work)).Catch();
示例五: Dictionary.TryGetValue
var data = new Dictionary<int,string>();
string result = null;
if(data.TryGetValue(1, out result))
{
Console.WriteLine($"已找到Key为1的结果:{result}");
}else
{
Console.WriteLine($"未找到Key为1的结果");
}
在Monads.net中:
data.With(1).Return(_ => $"已找到Key为1的结果:{_}", "未找到Key为1的结果").Do(_ => Console.WriteLine(_));
C# 函数式编程及Monads.net库的更多相关文章
- 从函数式编程到Ramda函数库(二)
Ramda 基本的数据结构都是原生 JavaScript 对象,我们常用的集合是 JavaScript 的数组.Ramda 还保留了许多其他原生 JavaScript 特性,例如,函数是具有属性的对象 ...
- 从函数式编程到Ramda函数库(一)
函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比, ...
- boost 的函数式编程库 Phoenix入门学习
这篇文章是我学习boost phoenix的总结. 序言 Phoenix是一个C++的函数式编程(function programming)库.Phoenix的函数式编程是构建在函数对象上的.因此,了 ...
- javascript函数式编程(一)
一.引言 javascript函数式编程在最近两年来频繁的出现在大众的视野,越来越多的框架(react,angular,vue等)标榜自己使用了函数式编程的特性,好像一旦跟函数式编程沾边,就很高大上一 ...
- 【大前端攻城狮之路】JavaScript函数式编程
转眼之间已入五月,自己毕业也马上有三年了.大学计算机系的同学大多都在北京混迹,大家为了升职加薪,娶媳妇买房,熬夜加班跟上线,出差pk脑残客户.同学聚会时有不少兄弟已经体重飙升,开始关注13号地铁线上铺 ...
- 翻译连载 | 附录 C:函数式编程函数库-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言
原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 译者团队(排名不分先后):阿希.blueken.brucec ...
- 给 JavaScript 开发者讲讲函数式编程
本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只 ...
- 一文带你了解JavaScript函数式编程
摘要: 函数式编程入门. 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 函数式编程在前端已经成为了一个非常热门的话题.在最近几年里,我们看到非常多的应用程序代码库里大量使用着函 ...
随机推荐
- BZOJ1228或洛谷2148 [SDOI2009]E&D
BZOJ原题链接 洛谷原题链接 完全不会呀.. 写了这题才知道\(SG\)函数原来也能打表找规律... 题解请看大佬的博客 #include<cstdio> using namespace ...
- Eclipse创建Spring项目 未完
使用的软件及版本 1)Eclipse:Eclipse Java EE IDE for Web Developers :Version: 2018-09 (4.9.0) 2)JDK:java versi ...
- mysql索引相关
索引有主键索引.唯一索引.普通索引 单列索引,复合索引. 复合索引(a,b,c),可以理解是有三个索引,分别是a.b.c三个索引 前缀不是a的话,复合索引都不起作用,前缀用函数或者是范围,比如< ...
- InputMethodManagerService处理输入法——监听APK变动
android\frameworks\base\services\java\com\android\server\InputMethodManagerService.java public Input ...
- wireshark源码分析 一
因为手头的项目需要识别应用层协议,于是想到了wireshark,打算在项目中集成wireshark协议分析代码.在官网上下了最新版的wireshark源代码,我的天啊,200多M,这么多代码文件怎么看 ...
- 【Web】CSS实现绝对定位元素水平垂直居中
网页中常常需用让绝对定位元素水平垂直居中,下面介绍2种方法: 一 元素宽度未知 <!DOCTYPE html> <html lang="en"> <h ...
- canvas 实现贪吃蛇游戏
var canvas = document.getElementById('canvas'); var cxt = canvas.getContext('2d'); // 定时器 var timer; ...
- php中如何配置项目虚拟路径
php虚拟目录的设置在apache目录下打开conf->httpd.conf文件,找到<IfModule dir_module>,在</IfModule>后面添加如下代码 ...
- 计算给定多项式在给定点X处的值
//计算多项式求值 //计算多项式求值#include<iostream>#include<ctime>#include<cmath>using namespace ...
- 阿里云oss怎么上传文件夹
最近公司在做工程项目,实现文件夹云存储上传 网上找了很久,发现很多项目都存在一些问题,但还是让我找到了一个成熟的项目. 工程: 对项目的文件夹云存储上传功能做出分析,找出文件夹上传的原理,对文件夹的云 ...