C# 函数式编程:LINQ(5)

但是 C# 中并不是所有的泛型类是自函子,例如 Task<T>,如果我们不为它添加 Select 拓展方法,它连函子都算不上。所以如果把 C# 中全部的自函子类型放在一个集合中,然后把这些自函子类型之间用来做类型转换的全部函数(例如,list.ToArray() 等)看作是态射,那么我们就构建出来了一个 C# 中的“自函子范畴”。在这个范畴上,我们只能对 Monad 类型使用 LINQ 语法进行复合运算,例如上面的:

// 原版
var result =
from a in taskA
from b in taskB
from c in taskC
select a * b + c;

// 1. 满足结合律
var left =
from a in taskA
    from t in (
        from b in taskB
        from c in taskC
        select new {b, c}
    )
select a * t.b + t.c;

var left =
from t in (
    from a in taskA
    from b in taskB
    select new {a, b}
)
from c in taskC
select t.a * t.b + c;

left == right
// true

// 2. 存在单位元

var left =  from a in Task.FromException(null)
            from b in taskB
            select a + b;

var right = from b in taskB
            from a in Task.FromException(null)
            select a + b;

// 因为 left right 得到的都是 Task.FromException(null) 的返回值,故 Task.FromException(null) 是单位元

由于这种作用在两个 Monad 上面的二元运算满足交换律且 Monad 中存在单位元,与群论中幺半群的定义比较类似,所以,我们也把 Monad 称为“自函子范畴上的幺半群”。尽管这句话听起来十分的高大上,但是却并没有说明 Monad 的特征所在。就好比别人跟你介绍手机运营商,说这是一个提供短信、电话业务的公司,你肯定不知道他到底再说哪一家,不过他要是说,这是一个提供 5 元 30 M 流量包的手机运营商,那你就知道了他指的是中国移动。

个人体会

其实我一开始想写的内容只有 LINQ to Result 跟 LINQ to Task 的,但是在编写代码的过程中,种种迹象都表明着 LINQ 跟函数式编程中的 Monad 有不少关系,所以就把剩下的函数式编程这一部分给写出来了。

Monad 作为函数式编程中一种重要的数据类型,可以用来表达计算中的每一小步的功能,通过 Monad 之间的复合运算,我们可以灵活的将这些小的功能片段以一种统一的方式重组、复用,除此之外,我们还可以针对特定的需求(异步、错误处理、懒惰计算)定义专门的 Monad 类型,帮助我们以一种统一的形式将这些特别的功能嵌入到代码之中。在传统的面向对象的编程语言中 Monad 这个概念确实是不太好表达的,不过有了 LINQ 的帮助,我们可以比较优雅地将各种 Monad 组合起来。

用 LINQ 来对 Monad 进行运算的缺点,主要就是除了 SelectMany 之外的,我们没办法定义其他的能在 Query 语法中使用的函数了,要解决这个问题,请关注我的下一篇文章:“F# 函数式编程:Computational Expression”(挖坑预备)。

参考资料

https://zh.wikipedia.org/zh-hans/函子

https://en.wikipedia.org/wiki/Monad_(functional_programming)

Linux公社的RSS地址https://www.linuxidc.com/rssFeed.aspx

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f5cc450b18cf816a73d43c0544f93dd5.html