函数式编程中,一切皆为函数,这个函数一般不是类级别的,其可以保存在变量中,可以当做参数或返回值,是函数级别的抽象和重用,将函数作为可重用的基本模块,就像面向对象中一切皆为对象,把所有事物抽象为类,面向对象编程通过继承和组合来实现类或模块重用,而函数式编程通过局部套用来实现函数重用;两种编程模式相辅相成,各有侧重点。函数式编程涉及高阶函数,纯函数、引用透明、闭包、局部套用、部分应用、惰性求值、单子等概念。

  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

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中:

    Person person = null;
      //person = new Person();
     person.Recover(p => new Person());
 

  示例四: 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库的更多相关文章

  1. 从函数式编程到Ramda函数库(二)

    Ramda 基本的数据结构都是原生 JavaScript 对象,我们常用的集合是 JavaScript 的数组.Ramda 还保留了许多其他原生 JavaScript 特性,例如,函数是具有属性的对象 ...

  2. 从函数式编程到Ramda函数库(一)

    函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比, ...

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

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

  4. javascript函数式编程(一)

    一.引言 javascript函数式编程在最近两年来频繁的出现在大众的视野,越来越多的框架(react,angular,vue等)标榜自己使用了函数式编程的特性,好像一旦跟函数式编程沾边,就很高大上一 ...

  5. 【大前端攻城狮之路】JavaScript函数式编程

    转眼之间已入五月,自己毕业也马上有三年了.大学计算机系的同学大多都在北京混迹,大家为了升职加薪,娶媳妇买房,熬夜加班跟上线,出差pk脑残客户.同学聚会时有不少兄弟已经体重飙升,开始关注13号地铁线上铺 ...

  6. 翻译连载 | 附录 C:函数式编程函数库-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  7. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 译者团队(排名不分先后):阿希.blueken.brucec ...

  8. 给 JavaScript 开发者讲讲函数式编程

    本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只 ...

  9. 一文带你了解JavaScript函数式编程

    摘要: 函数式编程入门. 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 函数式编程在前端已经成为了一个非常热门的话题.在最近几年里,我们看到非常多的应用程序代码库里大量使用着函 ...

随机推荐

  1. java.lang.AbstractMethodError: javax.servlet.jsp.JspFactory.getJspApplicationContext(Ljavax/servlet/ServletContext;)Ljavax/servlet/jsp/JspApplicationContext;

    在Tomcat下部署应用时会报这个错误,参考以下这篇博客:http://blog.csdn.net/robinsonmhj/article/details/37653189,删除Tomcat目录下we ...

  2. 开发中,android手机WIFI无法使用,无SIM卡故障解决

    用eclipse 开发android中,突然出现,android手机WIFI无法使用,无SIM卡故障解决 发现故障后,想办法刷机(没有成功),触点清洁都搞了. 最后恢复出厂设置居然解决了,留资料给同行 ...

  3. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  4. Vue修饰符

    为了方便大家写代码,vue.js给大家提供了很多方便的修饰符,比如我们经常用到的取消冒泡,阻止默认事件等等~ 目录 表单修饰符 事件修饰符 鼠标按键修饰符 键值修饰符 v-bind修饰符(实在不知道叫 ...

  5. jquery ajax 全局事件

    jquery的ajax方法的全部全局事件:(不管是$.ajax().$.get().$.load().$.getJSON()等都会默认触发全局事件) ajaxStart:ajax请求开始前 ajaxS ...

  6. 如何在Eclipse下安装SVN插件——subclipse

    如何在Eclipse下安装SVN插件——subclipse | 浏览:2799 | 更新:2014-09-20 22:39 1 2 3 4 5 6 分步阅读 版本控制是开发人员必不可少的工具,而SVN ...

  7. [ASP.NET]static变量和viewstate的使用方法

    在.Net平台下进行CS软件开发时,我们经常遇到以后还要用到某些变量上次修改后的值,为了简单起见,很多人都习惯用static来定义这些变量,我也是.这样非常方便,下一次调用某个函数时该变量仍然保存的是 ...

  8. C#中隐式运行CMD命令行窗口的方法

    using System; using System.Diagnostics; namespace Business { /// <summary> /// Command 的摘要说明. ...

  9. Linux服务器上新增开放端口号

    开放端口的方法: 方法一:命令行方式               1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT    ...

  10. 2019.01.22 hdu5195 DZY Loves Topological Sorting(贪心+线段树)

    传送门 题意简述:给出一张DAGDAGDAG,要求删去不超过kkk条边问最后拓扑序的最大字典序是多少. 思路:贪心帮当前不超过删边上限且权值最大的点删边,用线段树维护一下每个点的入度来支持查询即可. ...