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

  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. Android.Libraries

    1. Android Dependencies, Referenced Libraries, Android Private Libraries Android Private Libraries - ...

  2. Oracle数据库mybatis 插入空值时报错(with JdbcType OTHER)

    参考文档: 1.https://blog.csdn.net/fishernemo/article/details/27649233 2.http://helgaxu.iteye.com/blog/21 ...

  3. CH#17C 舞动的夜晚

    原题链接 即求二分图的不可行边数量,因为不保证是完备匹配,所以需要通过网络流求出任意一组最大匹配,并建立新图判断. 建新图:对于跑完网络流的图上已经匹配的边,建立反边:对于没有匹配的边,建立正边(图只 ...

  4. nodejs 后台开发 和C++代码开发

    https://www.npmjs.com/package/node-gyp node-gyp Node.js native addon build tool Node.js native addon ...

  5. BZOJ 1977[BeiJing2010组队]次小生成树 Tree - 生成树

    描述: 就是求一个次小生成树的边权和 传送门 题解 我们先构造一个最小生成树, 把树上的边记录下来. 然后再枚举每条非树边(u, v, val),在树上找出u 到v 路径上的最小边$g_0$ 和 严格 ...

  6. sql 用Group by分组后,取每组的前几条记录

    转自:http://blog.163.com/jeson_lwj/blog/static/135761083201052411115783/ --查询每门课程的前2名成绩 CREATE TABLE S ...

  7. DHT

    DHT(Distributed Hash Table,分布式哈希表)类似Tracker的根据种子特征码返回种子信息的网络.DHT全称叫分布式哈希表(Distributed Hash Table),是一 ...

  8. spring学习九 spring aop详解

    本文来自于:https://www.cnblogs.com/jingzhishen/p/4980551.html AOP(Aspect-Oriented Programming,面向方面编程),可以说 ...

  9. DOM-查找和修改

    1. 查找: 按HTML查找: 问题: 每次只能按一个条件查找,如果查找条件复杂,则步骤很繁琐 解决: 选择器: 按选择器查找: 2个API 1. 只查找一个元素: var elem=parent.q ...

  10. input.file样式修改

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...