概念
- 命令式编程的代码由一系列改变全局状态的语句构成,而函数式编程则是将计算过程抽象成表达式求值。
- 这些表达式由纯数学函数构成,而这些数学函数是第一类对象(我们可以像操作数值一样操作第一类对象)并且没有副作用。由于没有副作用,函数式编程可以更容易做到线程安全,因此特别适合于并发编程。
为什么在并发和并行问题时会用到函数式编程
- 有关锁的一些规则,都是针对于线程之间共享的可变的数据——换个说法就是共享可变状态。而对于不变的数据,多线程不使用锁就可以安全地进行访问。
- 这就是为什么在解决并发和并行问题时函数式编程会如此引人注目——它没有可变状态,所以不会遇到由共享可变状态带来的种种问题。
- 纯粹的函数式语言中,函数都具有引用透明性——在任何调用函数的地方,都可以用函数运行的结果来替换函数的调用,而不会对程序产生副作用。这个特性也使得可以任意安排多个计算过程的求值顺序,包括让它们并行。
- 所有函数(至少是理论上)都可以同时执行。这种执行方式被称为数据流式编程(dataflow programming)。
- PS:其实本书在第三章函数式编程部分的前两天中的内容,在我看来更多的是利用语言或者运行时本书的并发能力。后续会专门写个文章总结下C#中对应的并发功能。
写在2018.2.8的第一版总结
- 函数式编程确实是个大话题,在七周七并发模式看到第三章的时候我卡到了函数式编程这里。然后看完了《函数式编程思维》这本书,《C#函数式程序设计》还在路上,我想这是一次很不错的机会,让我好好的学习下。
参考
从C#的角度看看
- Map、Reduce、Filter分别对应了C# linq中的三个函数
1 | Map = Select | Enumerable.Range(1, 10).Select(x => x + 2); |
- Map从下面函数式编程部分的含义看就是接收一个函数,作用于范畴中每一个值,使得范畴从A变成B。
- Filter就是过滤,在我理解里面其实也就是一个传递给map的函数,这里的map是函子中的map。
- Reduce,正如下图说的,它是个折叠的作用。要我说就是sum的过程。只不过也许并不是真的对数值进行累加,而是可以做其它的处理。总之细细体会下面图片中的文字吧。
- PS:折叠这个词用的好。
来自知乎的一个回答
结论:函数式编程会把数据的结构外显,而命令式则把执行过程外显。
或者这样说:你在读函数式代码时,经常会想不清楚执行过程;而你在读命令式代码时,会经常搞不清楚当前对象有哪些属性。
函数式编程思维
函数式编程
原文
范畴
- 彼此之间存在某种关系的概念、事物、对象等等,都构成”范畴”。随便什么东西,只要能找出它们之间的关系,就能定义一个”范畴”。
- 范畴的数学模型:
- 所有成员是一个集合
- 变形关系是函数
容器
- 可以把”范畴”想象成是一个容器,里面包含两样东西。
- 值(value)
- 值的变形关系,也就是函数。
范畴论与函数式编程的关系
- 范畴论使用函数,表达范畴之间的关系。
- 本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。
- 为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。
- 总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。
函子
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
++它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系++。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器—-函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。
PS:实际上函子通过map方法接收函数来实现转换。而所有的计算本质还是这个函数对函子中的值的计算
常见的函子
of
- new一个函子
Maybe
- Maybe 函子就是为了解决内部值为null而设计的。简单说,它的map方法里面设置了空值检查。
Either
- 条件运算if…else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。
- Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。
ap
- 函子B内部的函数,可以使用函子A内部的值进行运算。这时就需要用到 ap 函子。
- ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。
- ap是为了解决一个函子使用另一个函子的值的问题。
Monad
Monad 函子的作用是,总是返回一个单层的函子。
它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
Monad 函子的重要应用,就是实现 I/O (输入输出)操作。
举例说明:
1 | class Monad extends Functor { |
- 上面第一段代码中如果f函数返回一个函子,map本身也是返回函子。这样就的造成了this.map(f)是函子的嵌套。
- 从事例的角度看,
readFile('./user.txt')
本身是返回了一个函子IO,其中的值为函数function() { return fs.readFileSync(filename, 'utf-8');}
(注意:函子的值可以是函数,而且因为这里的new实际就是上面的of,所以里面的参数应该是值),它其实是从文件中读取到的数据。而.flatMap(print)
根据上面的描述它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
。它取出的就是第一个IO函子中的值(函数),然后在将这个值作为参数传递到print函数中。只有就形成了函数链。 - PS:以上只是我的理解,这篇文章的评论区有很多质疑,但是因为我确实对这个不了解,所以就按照原文的理解来解释了。