记读到的函数式编程思想

函数式编辑的目的

学习函数式编程有一段时间了,之前也总结过一些函数式编程的知识点。然后,现在觉得之前对函数式编程的认识太肤浅了,只停留在几个概念的解释上。最近又抽空看了一下这方面的东西,有了更多的理解和感受,简单记录一下。

一切要从Lambda演算说起。

最开始,邱奇试图创制一套完整的形式系统作为数学的基础,当他发现这个系统易受罗素悖论的影响时,就把lambda演算单独分离出来,用于研究可计算性,最终导致了他对判定性问题的否定回答。话说当年阿隆佐·丘奇搞出的这套Lambda演算,与图灵(图灵的博导正是邱奇)机是等价的。丘奇从数学角度出发提出了这个形式系统,其定义只有三条规则。这短小精悍的Lambda演算,日后也生根发芽长出了函数式编程这朵奇葩(虽说奇葩也是花,然后大众眼里函数式编程真的是个“奇葩”,原因稍后再讲)。

Lambda表达式有三个形式(分别是变量、函数、函数应用)

E = x     variables
| λx. E function creation(abstraction)
| E1 E2 function application

分别是变量、函数、函数应用。总共就这三个形式,很简单有没有?

Lambda演算有几个特点:

  1. 函数只接受一个参数并且只返回一个值
  2. 是可计算的

只接受一个参数好像功能有限制,但是通过柯里化技术,多参是可以实现的。其实,Lambda演算可以表示(编码)自然数、逻辑谓词、布尔值等编程语言里常见的类型。函数可计算的意思是说传进一个参数经过一段算法逻辑后在有限的资源条件下会返回一个计算结果。

我们这里说的Lambda演算是无类型Lambda演算。什么是无类型?就是没有数据类型申明。顺带说一下有类型的Lambda演算之后成为了编程语言的基石,而无类型Lambda演算又对函数式编程语言,如LISP影响非常深远。

函数最重要的特点就是一个参数对应的返回值永远是同一个。这种特性我们很熟悉,就是初中数学课上学到的函数的定义:

函数是不同数值之间的特殊关系:每一个输入值返回且只返回一个输出值。

相同参数返回相同值的特性使我联系到javascript里的对象与数组,一个对象里保存的是数量有限的键值映射关系,如果把这个对象扩大到无限大,那么从结果上看它与功能相同的函数有什么区别?这么想想javascript里函数是一个特殊的对象就很容易理解了。

利用这个特性我们可以很容易实现一个可缓存的函数,如果参数相同,就把里面的过程省略掉,直接返回缓存好的计算结果。类似的优化还可以做到语言的编译器里,在函数式语言里,如果编译器发现了你的代码存在某些可优化的算法,它可能会把你的代码逻辑替换成最终的结果来进行优化。

函数的这种特性使得我们的代码是易测试的、可预期,同样的参数永远返回相同的结果,而且它还不会对不该它管的外部变量,甚至它的参数有任务影响。完美!这不就是程序员梦寐以求的程序吗?没有完美到可以不出错,而能完成任何任务的程序,但是起码能指哪打哪,出了BUG可以很快定位到问题点。这就是一个可控的代码。

函数式编程可能有很多(奇怪难懂的)特性和很多好处,但是在我看来,纯函数的可预测性、没有副作用是我使用函数式编程方式最重要的目的。


保持“纯函数”的理论方法

为了保持函数的“纯”,函数式编程语言有许多奇技淫巧。其主要思想是减少变量,没有变量就可以避免中间环节对状态的修改。而为了管理讨厌的状态,函数式程序引入了 Monda 这个数学概念。

关于减少变量,函数式编程里经常使用柯里化提前绑定参数来生成新的函数、以及函数组合等手法来达到目的。因此,这种代码风格的代码量比较少,但是套的层多了就头痛了,阅读代码的时候得在大脑里做一个解码才行。

函数式编程里很少看到 if else for while 这些逻辑控制语句,而是以 map reduce filter 等函数替代。这么做使得代码片段都变得了一个个可靠的函数体,利用一些概念复杂使用简单的方法可以方便组合成更复杂的程序。因为不关心里面的计算过程,相互之间没有又状态的依赖,只关系各个函数体(模块)间的输入输出,使用我们有可能把某个模块热替换掉而不影响整体功能。而且统一的函数风格非常方便把代码组合成复杂的程序。

下面是一个javascript的组合函数

var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};

使用的时候是这样的

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"

这种组合的方式弱化了过程,注重声明,大大提高了代码的利用率。

另一方面,引入的 Monda 使得逻辑过程和状态的保持变得透明,数据在各个流程处理的函数流过,不要费心维护一个全局的状态。

常见的 Monda 有 Maybe Monda ,IO Monda ,Task Monda , Either Monda 等。每种 Monda 对应处理一种问题,比如 Maybe 处理 null, IO 处理输入输出。在 javascript 里还有一种常见的 Monda 经常用过处理异步调用问题,那就是 Promise 。

Monda 解释起来很麻烦,它指的是一种容器,对数据有统一的输入处理,转化成它可以处理的安全的数据。它也是一个键值的映射。关于 Monad 推荐看一下这个教程的第8–9章。

参考链接:

https://en.wikipedia.org/wiki/Lambda_calculus
https://en.wikipedia.org/wiki/Monad_(functional_programming)
https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese

Like what you read? Give FusionBin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.