Lambda Academy

我是怎样走上函数式编程的不归路的

December 04, 2018

前几天的一篇文章引来了一些争议。文章收到一些反对意见,其中大部分属于无理取闹和误读,但也有认真探讨的。网友 D 表达了他对于函数式编程的傲慢的不满。我在这里回复一下。

一,这场争论是怎样产生的

我在之前的文章也提到过我最初写《如何在 JS 代码中消灭 for 循环》(下面简称“消灭 for 循环”)这篇文章的背景。当时进了新公司,发现代码库里甚少看到高阶函数,迭代几乎都是用 for 循环实现的。为了让别人看懂这些代码,同事们也写了大量注释。而我觉得写代码本不该如此费力,如果用点抽象的话,不仅能让代码更容易读,易写,甚至都不用写注释。如果前同事看到了这篇文章,还望见谅,我纯粹从技术层面上提出我的改进想法,没有任何 diss 的意思。大家共事也很愉快,前同事们也很上进爱学。

我相信像 D 那么优秀的开发者,看到这种代码,应该不会很愉快。再怎么反感函数式的霸权,代码中适量的过程抽象总该要有吧?当时我总结了我过去一年中学到的相关的知识,做了一次技术分享,后来把这次分享内容发到掘金了,然后才有后来的故事。

以上为背景。下面我讲下我那篇文章的一些影响源。

首先是 Joel Thoms 在 Hacker Noon 发表的 Rethink JavaScript 系列文章。我在还没找到工作时就看完这个系列了。这里我要承认我的一些不足。我因为不是科班出身,所以对学习到的新知识,无法像 D 那样将其放到 CS 整个背景中去验证和反思。我都是看作者资历,几乎全盘接受。Joel Thoms 的自我介绍是 Computer Scientist and Technology Evangelist with 20+ years of experience with JavaScript! 这么牛逼的 Title,我一个初学者当时还没办法去质疑。

然后就是我之前提过的 Dr. Boolean. 他基本上不用 for 循环,也经常 diss for 循环。再次推荐 A Million Ways to Fold in JS

Joel Thoms 的 Death of the For Loop 为我写“消灭 for 循环”壮了胆。甚至用递归代替迭代这么有争议的内容,我当时也是以一种有巨人站在背后撑腰的心态写出来了。

二,我为什么学函数式编程

从我之前分享的学习资源和上面介绍的背景,大家应该能看出来我学编程主要是学的来自美国的内容,而美国的前端届,目前函数式编程确实处于霸权地位。先消除下误会,我不是说因为是美国的,所以先进。这里只是介绍背景。在这个背景下,我学习函数式编程是很自然的一件事。事实上,我一开始学编程就在有意识地学习函数式编程

这里要介绍两个女生,大家应该很少有人听过。

一是 Anjana Vakil,她本科学哲学,研究生学语言学,典型文科生。现在她是一家叫 Mapbox 公司的 Engineering Learning and Development Lead。2016 年年底,我已近决定开始转行学编程了,当时 JS 还只看了一点点,没入门。偶然一次机会在 YouTube 看到 Anjana 的演讲,主题是 Learning Functional Programming with JavaScript

演讲开场白是这样的(这里我就不秀英文了,直接翻译了):“大家好,我是 Anjana, 我第一份工作是英文老师,后来去做了计算语言学家,然后我又来做软件开发了。就在 6 个月前,我根本不懂什么是函数式编程,也不会 JS。那么现在呢,我来给大家分享下怎么用 JS 做函数式编程。”当时很受她的故事鼓舞,所以这个演讲看了好几遍。所以,我在 JS 还没入门,还不懂 this 指针和高阶函数的时候,就已经知道别人说的函数式编程的好处了。我也是在这个时候听说了 Ramda 很强大,然后记下来了以后学。

二是 Preethi Kasireddy,她的故事也很传奇。她是学商科的,后来进了 Andreessen Horowitz 这种顶级投行工作。别人抢破头的金饭碗,她觉得没意思,辞职后从零开始学编程,从事软件开发了。她在 Medium 上发表了多篇关于区块链的热文,你可能看过。她一开始是在 freeCodeCamp 学习,在社区里面比较活跃。刚好我也是在 2017 年初了解到 FCC。她很聪明,一开始就有意识采用费曼学习法,用教的方式来学。她的方法是在网上做一个 public commitment,告诉社区的人,我要开始给你们教 JS 函数式编程了,报名参加吧。这样子她就有动力去准备材料然后教给别人了。然后我就报名参加了。现在她那篇宣布这项活动的文章还在:Learn the fundamentals of functional programming — for free, in your inbox 那个时候我的 JS 算勉强入门,知道一些 API,但是几乎没练习过代码。所以,学了 Preethi 的教程,也只是留下了印象。

这两个故事我本打算加进之前写的学习经验分享帖里面的,但那篇文章已经足够长了,所以没加。

后来就是 2017 年 5 月份,Eric Elliott 开始连载 Composing Software 系列文章,然后我就一路学过来了。

从上面的经历可以看出,我几乎没有面向对象编程的经历。当然,为了应付面试,我还是去补了一下多态,封装,和继承这些面向对象程序设计知识,尽管我对这些概念很无感。

所以,我对面向对象的认知,几乎全是二手观点。而对函数式编程的认知,则是自己在编程练习中丰富起来的。

三,坚持函数式编程

其实我也不用以一种捍卫信念的姿态来回应 D,因为他也没全盘否定 FP。他的观点我也不想一一回应,因为我觉得好累,我要把我过去学到的东西再总结下然后去回应对方,我好想直接扔链接……

其实这些争论是源自函数式编程和面向对象编程之间几乎水火不容的对立造成的。我更亲近函数式编程,所以坚持的一些观点,就显得排斥过程式和指令式的代码。很遗憾的是我目前认知有限,不知道怎么调和这种矛盾。

我还是想从一个初学者的角度,讲讲为什么函数式编程值得坚持。我只想讲最核心的,就是函数式编程利用程序组合来解决问题的思路,会让我觉得更容易拆解复杂问题。让我们暂时搁置“组合优于继承”这种教条,去看看程序组合是怎么解决问题的。对于不管多么复杂的任务,我们都能将其拆解成小任务。而这些小任务,也有可能在其它复杂任务里面用到。为了尽量复用我们拆出来的这些小任务,我们要保证它没有副作用。这也是为什么函数式编程这么排斥副作用。在我们做了合理拆分之后,再把这些体现为纯函数的小任务组合起来,就实现了程序功能。这些简单描述我在之前的文章中有大量代码例子,这里就不展示了。

而程序组合就需要用到高阶函数 compose 或者 composeK(来自 Ramda 的 Kleisli Composition)。很多人觉得高阶函数难懂,而且觉得用高阶函数的人是在炫技。这个真的无解了,Redux 里面有 compose 的啊…… 而且 pipe 操作符提案现在是 Stage 1,很有可能成为语言标准。到时候函数组合会获得一级语言支持。

D 文章里面有提到计算机底层的部分。这里我不惭愧地说下我还不是很了解底层,我还在学,过一两年再来聊这个话题吧。这里我扔一本书吧,程墨的《深入浅出 RxJS》里面第一章第 11 页。打字太累就不复述了。

D 用来嘲讽函数式编程的“自函子上的幺半群”也不成立。这句话不过是用术语精炼概括一个概念,是没问题的。再说这句话甩锅给 FP 是不对的,因为它来自范畴论。而范畴论是一种高度抽象的知识,一层一层的抽象,让你只能用较低阶抽象概念去简短解释高阶抽象概念。我想门外汉如果去听理论物理学家讨论专业知识,是不会为他们用术语解释术语而不满的吧。如果想听懂他们的对话,你也只有先去学这些术语。

最后,Promise 不是 Monad, 见链接 No, Promise is not a monad 未来我会写文解释什么是 Monad,并说清楚这一部分。

四,悬置争论

我在这条学习路线上学了一段时间后,不可避免地发现 JS 社区里面是有派系纷争的。而作为一个没多少经验的开发小白,要在这些纷争里站队也是很痛苦的一件事。你应该选择有道理的还是选择占上风的?为了混口饭吃,我也只有选择占上风的,毕竟这个才是业务开发里面的政治正确。

这里还是要举 Promise 的例子。2013 年,社区里面起草 Promise/A+ 标准草案的时候,有些 FP 开发者提议让 Promise 更符合 monadic interface,大概就是要在 JS 里面实现 Haskell 的 Future。然后起草委员会的人回复说:

Yeah this is really not happening. It totally ignores reality in favor of typed-language fantasy land

傲娇的 FPer 们不干了,赌气杠上了,就另起炉灶争锋相对搞了个 Fantasy Land Specification。还有人觉得我靠这 JS 算没救了,我们还是逃难吧,于是基于 Fantasy Land 的标准,有人搞了个 Sanctuary.js。现在你还能在 GitHub 上看到这句评论,以及 FPer 被激怒后留下的那 220 个 down vote(我打开看时是 219 个,于是我加了一票……)

围绕着 Fantasy Land 建立的社区目前也很小众,有点像经济学里面的奥地利学派…… 这些人大概开发比较自由,可以用各种 Fantasy Land 库,PureScript 和 Elm 等语言来避免丑陋的 JS。

你可以看这篇文章 Broken Promises - The unspoken flaws of JavaScript Promises 看 FP 开发者是怎么痛批 Promise 的,是不是有道理,以及他们提倡的替代方案是什么。

这些纷争暂时是没有答案的,各有道理。我心里有自己的偏好,但就像刚刚讲的,为了混口饭吃写业务,还是乖乖折中下吧……


Lambda Academy

Lei Huang

这个博客我主要分享函数式编程和前端开发。我还有一个英文网站