本文作者: Eric Elliott
编译:胡子大哈

翻译原文:http://huziketang.com/blog/posts/detail?postId=58e3062ba58c240ae35bb8de
英文连接:The Rise and Fall and Rise of Functional Programming (Composing Software)

转载请注明出处,保留原文链接以及作者信息

本文是“组合式软件”系列的一篇文章,从头开始学习函数式编程和使用 JavaScript ES6+ 进行软件编程。请继续关注,后面还有很多相关内容。

在我 6 岁的时候,我每天花很多时间和我最好的朋友一起打游戏,他们家里有很多电脑。对我来讲它有种魔幻版不可抗拒的力量。有一天我突然问我朋友:“我们怎样才能自己做一个游戏呢?”

他也不知道,所以我们一起去问他的爸爸,叔叔从很高的架子上拿下来一本书:Basic。我也由此开始了我的编程之旅。后来大学都有代数这门课程,而我对它已经很熟悉了,因为编程里面代数是基础,到处都是代数。

组合式软件的兴起

在计算机科学刚刚起步的时候,很多计算机科学的理论都还没有落地。那时候有两个伟大的计算机科学家:阿隆佐·丘奇和阿兰·图灵。他们创造了两个不同的,但是具有同等效力的通用计算模型。两个模型都可以计算任何可以计算的东西(着重强调,“通用”)。

阿隆佐·丘奇发明了 λ 演算, λ 演算是基于函数应用的通用计算模型。阿兰·图灵则因图灵机而广为人知。图灵机定义了一个理论上的设备,它可以控制条带上的符号。他们合作证明了 λ 演算和图灵机是功能等价的。

λ 演算全部都是关于函数组合。函数组合在软件开发中是非常富有表现力和说服力的。本文中,我们会讨论函数组合在软件设计中的重要性。

这里有三点关于 λ 演算的特殊说明:

  1. 函数通常是匿名的。在 JavaScript 中,const sum = (x, y) => x + y 的右边是匿名函数,即 (x, y) => x + y

  2. λ 演算中的函数只接受单一输入,它是一元的。如果你需要传递多参数,函数会接受第一个输入并且返回一个新的函数来接受第二个参数,以此类推。一个 n 元函数 (x, y) => x + y 可以表达为一个一元函数:x => y => x + y。这种 n 元函数到一元函数的转化叫做柯里化。

  3. 函数是一级的。意思是说一个函数可以作为另一个函数的输入,并且一个函数可以返回另一个函数。

这些特征一起构成了简单且容易理解的规则,在组合式软件中使用函数作为主要编码单元。在 JavaScript 中,匿名函数和柯里化函数都是可选特征,也就是说 JavaScript 支持 λ 演算的主要特征但是并不强制使用。

经典的函数组合是把一个函数的输出作为另一个函数的输入,例如下面的组合:

    f·g

可以写成:

    compose2 = f => g => x => f(g(x))

下面是如何使用它:

    double = n => n * 2
    inc = n => n + 1
    compose2(double)(inc)(3)

compose2() 函数接受 double 函数作为第一个参数,inc 函数作为第二个参数,最后应用参数 3 到这两个函数组合上。再看一下 compose2() 的声明,fdouble()ginc()x3。函数调用 compose2(double)(inc)(3),实际上是三个不同的函数调用:

  1. 首先传递 double 返回一个新函数 1;

  2. 新函数 1 以 inc 为参数并且返回一个新函数 2;

  3. 新函数 2 以 3 为参数并且计算 f(g(x)),即 double(inc(3))

  4. x=3 传递给 inc()

  5. inc(3) 计算结果是 4;

  6. double(4) 计算结果是 8

  7. 最终返回结果是 8

组合式软件的过程可以用函数组合图来表达,看下面代码:

    append = s1 => s2 => s1 + s2
    append('Hello, ')('world!')

可以用图来模拟表示:

λ 演算对软件设计的影响是深远的,直到大约 1980 年,计算机科学界很多有影响力的品牌,都是采用函数组合的方式来开发自己的软件。Lisp 是 1958 年发明的,它深受 λ 演算的影响。直到今天,Lisp 是依旧广为使用的第二大历史悠久的语言。

我是通过 AutoLISP 知道的 Lisp,AutoLISP 是在最流行的电脑辅助设计(CAD)软件——AutoCAD,中使用的脚本语言。AutoCAD 太流行了,使得其他所有的 CAD 应用几乎都支持 AutoLISP 以保持其兼容性。Lisp 依然能够在计算机科学课程中广为使用有三个主要原因:

  1. Lisp 非常简单,基本上可以在一天之内学习完它的基本语法和语义;

  2. Lisp 基本上全部是函数组合,函数组合来做应用架构的方式非常优雅;

  3. 我所知道的最好的计算机科学课本使用Lisp:计算机程序的结构和解释

组合式软件的没落

在 1970 到 1980 年期间,软件开发的方式开始发生变化,简单的组合式开发不再受宠。出现了面向对象编程,它基于组件封装和信息传递的思想,在当时是非常先进的。代码通过继承来实现复用,继承关系是一种叫做 is-a 的关系。

函数式编程逐渐被边缘化,被抛弃到学术界和非主流的场外。在 1990 — 2010 年期间对三种人形成了甜蜜的困扰,一种是编程极客,一种是大学教授,一种是逃离了 Java 思想强制灌输的幸运的学生。而对于我们来讲,这 30 年的软件开发有一点噩梦般的感觉,黑暗的年代。

组合式编程的重新崛起

2010 年左右,有个巨大的变化:JavaScript 爆发了。在 2006 年以前,JavaScript 一直被认为是一种玩具式的编程语言,可以在浏览器中做一些很可爱的动画,但是在这背后隐藏着潜力巨大的特点,即 λ 演算的重要特征。人们开始逐渐在私下里谈论“函数式编程”。

到 2015 年,用函数组合来开发软件重新开始流行起来。为了简化使用,JavaScript 规范也做了 10 年以来的首次重大升级,增加了箭头函数。箭头函数使创建和使用函数、柯里化和 λ 演算变得很容易。

箭头函数对于 JavaScript 函数式编程的爆发起到了推动剂的作用。现在很少看到那种不用函数式编程的大型应用了。

组合的方式可以简洁清晰地描述软件的行为,把一些小的、确定性的函数组合成大的组件,进而形成软件,这样的软件很容易组织、理解、调试、扩展、测试和维护。

在读接下来文章的时候,希望你能通过例子自己动手做实验。回想一下当自己还是的孩子的时候,把一些东西拆开,自己再学着组装、拼接。重新找回童年探索事物的感觉,希望你能享受这个过程。

如果本文对你有帮助,欢迎关注我的专栏-前端大哈,定期发布高质量前端文章。


我最近正在写一本《React.js 小书》,对 React.js 感兴趣的童鞋,欢迎指点


胡子大哈
1.7k 声望1.6k 粉丝

前百度高级工程师,知乎“前端大哈”专栏作者,全栈架构师。