这是翻译自 Quora 一个问题 “What are some myths about functional programming and functional programming languages?” 的一个高票答案,我也不记得当初决定翻译它时的心情,不过历经多次组会,最终完工了。


下面是我能想到的,更具体的一些细节会在后面给出:

  • “函数式编程”是定义明确的概念
  • 函数式编程仅仅是命令式编程的特殊形式
  • 函数式编程本来就很难
  • 函数式编程本来就是复杂的
  • 函数式编程不宜编写图形用户界面
  • Haskell 仅仅是函数式的
  • 你需要大量的数学知识来进行函数式编程
  • 函数式程序员必须非常聪明而且擅长计算机科学

“函数式编程” 是一个(明确的)概念

它不是。它是计算机科学中定义最糟糕的术语之一 —— 甚至不如“面向对象”,但比“声明式编程”好一点点。弄清楚一种语言是否是“函数式的”是一个人类学(anthropology)上的练习而非计算机科学。

没有任何一种“函数式编程”的技术定义不会引发众人抱怨。有些定义过于宽泛 —— 如果你想要的是 lambda 表达式,那么一切从 C ++ 到 Java 到 Python 的语言突然都变得“函数式”了,只有 C 和汇编不是!如果你要一个数学上的定义,那么只有 Coq、Agda 和它们的朋友们是函数式的,因为就算是 Haskell 都允许部分函数和非终止性。还有异常。哦,上帝,异常!

就我个人来讲,称每一种带有“脉冲”的语言(即任何具有 lambda 表达式的语言,lambda 的符号为 λ,像一个脉冲)为“函数式的”并没什么用。当我说“函数式的”,我通常是指像 Haskell 或至少是 ML 的语言,而不是像 Java、Python、 JavaScript,或者甚至是 Common Lisp 的语言。Scheme 和 Clojure 有点是函数式的,但我不会对它们谈论过多,即便我的确有大量关于前者(Scheme)的经验。

好吧,实话说,我的意思只是“Haskell”,除非我明确提出不是。是啊,这是为了你们的语言学上的定义。(脑补出这个翻译)如果你花更少的时间用命令式语言和更多的时间阅读我的思维,你就会明白3。

当然,有些人倾向于给“函数式编程”一个更杂糅的定义!

函数式编程仅仅是少了某些东西的命令式编程

这也许是最有害的神话,因为它广泛流传,特别具有误导性,通常未被明确说出。人们随意假设进行函数式编程就像一个穿着紧身衣的“正常”编程。并不是!一点也不。相反,函数式编程为编程提供了一个新的基础。你使用不同的方式表达事情。通常是全然不同的。事实上,大多数时间,在你使用命令式方法完成的事情和使用函数式方法完成的事情之间,不存在一个一一映射。

在 Haskell 上这点最明显,因为它是唯一把函数式特性放在第一位的语言 。这实际上让 Haskell 比混合语言明显更有表达能力 —— 从确定性的并行到重写规则(即矢量融合)到 软件事务性内存(STM,Software Transactional Memory)到惰性(计算)到它大量的库,这一切都是因为 Haskell 的函数式核心才成为可能。同时,这也使得 Haskell 与其他语言如此不同。这是一种完全不同的思考方式,一个新的基础 —— 不是同样香肠的不同型号,就如许多其他的语言一样(还有 BMWs5)。

这使我想起一个小的主题神话: Haskell 和不纯的函数式语言之间的差异仅仅是是(纯净)程度上的,而非类别上的 。这绝对不真实。根据我的经验,Haskell 的纯(函数式)和惰性(计算)与 OCaml 或 Scala 等类似语言,存在与生俱来的实践和哲学上的重大差异。多范型语言的倡导者喜欢声称函数式和命令式都支持才最好,但并不是;在许多方面来看,Haskell 比混合语言表达能力更强。

例如,Haskell 中可以很容易地将计算具体化(reify)为数据,这可以得到更加模块化的代码。这可以很容易地将一个计算的定义与其求值解耦和。列表通常以数据结构的形式替代循环;树可以表示复杂的递归函数。反过来,这使得任意类型上的 foldunfold 操作在 Haskell 中比在其他语言中更强有力。

在类如 Scala 的语言中尝试模拟 Haskell 风格的函数式编程并不容易。Edward Kmett —— 一个领先的开源 Haskell 开发者 —— 甚至走得更远,他重写了一个新的 JVM 语言来克服这些限制;可以看一下他的关于“为何 Scala 不够” 的这一点上细致详细的论述。

寓意:不纯粹(的函数式特性)并没得到完全性的胜利;它不仅在安全性上做出了巨大牺牲,同时在表达能力上也是。 当然,不纯粹性也使得一些代码的实现更容易,但其中的大部分都被 Haskell 中的特性覆盖了,例如状态线程(ST,state threads)或者 IO。

所有这一切,也忽略了一些其他功能,哈斯克尔我提到这取决于纯度或懒惰像载体的融合,重写规则或确定性的并行性。

函数式编程原本就难

函数式编程与大多数人习惯的编程方式差别很大。很多人发现学习函数式编程很难,因为这就像完全重新学习如何编程。回忆一下你学习第一门语言时的感受,那么可以想象你将重新体验一遍。当然了,这看上去会很难!

从 Java 迁移到 C# 很简单。从 Java 到 Python 需要一点智力上的迁移,但是基本上还是那回事。甚至从 C 到 Java 也不太糟 —— 从概念的角度考虑,Java 是在和 C 同样的基础上增加的概念,两者都有变量、控制结构和表达式。从你最初学习的语言到当今流行的新命令式语言,这个过程是渐进的。

函数式编程并不是这样,这就像从你脚下抽走地毯一样。最基础的想法都被完全替换掉了。不再有语句(statement),不再有循环(loop),不再有变量。见鬼,不再有程序执行 —— 对于一段函数式程序,你不是运行它,而是对它求值。事实上,对于一门类如 Haskell 的语言,求值的顺序是在你的抽象层次之下的,并不影响你的程序的所作所为 —— 执行的顺序控制了作用(effect)何时发生,其同表达式怎样被求值是完全分离的。这意味着你书写程序的顺序很大程度上不再重要,而这对于命令式程序员来说很奇怪,因为命令式的思考方式要求你时刻将程序的执行顺序记在脑中。

函数式编程本质就是复杂的

同时,“复杂”也是“困难”的代名词。Rich Hickey 的“简单导致容易(Simple Made Easy)”2的演讲精彩地阐述了“复杂”和“困难”的关系。前者(简单还是复杂)是系统的一个属性,粗略地说,就是它有多大。后者是人的一个属性 —— 一件事情有多难很大程度上依赖于其经验和接受的教育。

有些人切身感觉函数式语言很难。但这并不意味着它是复杂的!事实上,你可以把一门类如 Haskell 语言的核心求值规则和类型规则塞到一张纸上。当然,你必须使用非常精确的数学符号才行,但这也仅在规则比较少时行得通。对 ML 语言也是如此。诚然,任何在现实世界中使用的语言,包括 Haskell,都会迅速滋长额外的复杂度。但至少,函数式语言依旧可以基于 λ 演算,保持一个最小的、简单的以及良好定义的内核 —— 这是命令式语言不能声称的特性。

一个重要的概念是实现的简单和语义的简洁的差别。函数式语言意在后者:它们以更复杂的运行时环境或编译器为代价,来追求更加一致的行为。命令式语言(以 Google 的 Go 语言作为一个极端例子)通常采用相反的方针:比起语义的简洁,更加注重实现上的简单。它们选择了不一致和未定义的行为,来换取一种简单的实现,此外还期待一种更加简单的与硬件的映射。

函数式编程适合编写图形用户界面(GUI)

函数式编程用来编写 GUI 再合适不过了!我们只是采用不同的方式而已。我们有一个编写 GUI 代码的全新6范式:函数反应式编程(FRP)。FRP 使 GUI 代码更简单,更加模块化,更具声明式特性。

GUI 代码是对时间的建模。使用命令式语言,时间是使用可改变的状态和回掉函数被隐式建模的。其中时间确凿无疑地是一个二等公民。这使得我们不能直接谈论时间,不可避免陷入回掉函数错综复杂的泥潭,纠缠在严重耦合的全局状态之中。当然如果你很小心,这可能只会变成半全局状态。你甚至不能拿一个变量,然后声称“当 x 大于 7 时,让这个变量变红;否则使它变蓝”。相反,你需要相当多的“仪式”以及外部结构来把 x 封装在一个模型之中(或者其他什么东西之中),还要带上事件监听器(event listeners)和常用的访问器(accessors)。

而使用 FRP,时间是你可以精确表示出来的东西。这就是我所说的让时间成为“一等公民”:你可以编写代码,直接引用变量值在时间上的行为。

查看什么是函数反应式编程?以及我的生命游戏中的例子 FPR | jelv.is (带有完整的代码Reactive-Life)。

FRP 不仅仅让我们可以编写漂亮的反应式 GUI 代码:它对于音乐以及甚至是机器人学中的应用也很好! 对于所有的这些应用来说,比起使用回掉函数和状态,这绝对是前进性的一步!

Haskell 仅仅是一门函数式语言

不,Haskell 是一门完全的多范式语言.它很容易就能支持命令式编程 —— 人们戏称它为“最好的命令式语言”或者“完美的 Algol”1 —— 以及逻辑式编程。它甚至能够支持面向对象编程(OOP),当然没有人给出足够的关注。Haskell 代码甚至可以看起来像 C 语言,当然要付出一点努力!

唯一的区别是,不同于其他任何一门多范式语言,Haskell 是函数式为先的。其他的语言给你一个命令式的基础,然后在此之上叠加函数式的功能。Haskell 给你一个函数式的基础,然后在此之上叠加命令式或者逻辑式的功能。

由于这是 Haskell 异于其他语言之处,同样也是非 Haskell 人员(non-Haskellers)纠缠之处。这也引发了初学者针对函数式语言提出的一堆无意义的讨论,例如“Haskell 为什么不允许值的改变(mutation)”。

你需要大量的数学知识才能使用函数式编程

实际上不需要。当然了,函数式语言是在数学基础上设计的 —— 同时命令式语言是在计算机体系结构基础上设计的。然而,你使用 C 语言时并不需要知道关于 ALU (Arithmetic Logic Unit,计算逻辑单元)的寄存器知识。

我在对除了基本的微积分外一无所知的情况下接触函数式编程的。此外,我从来没有特别擅长过数学。而这对我没有造成一点障碍。我很好地学习了实践意义上的 monads(单子)、applicatives 以及 functors(函子),在我理解任何相关理论之前。

实际上我是通过 Haskell 来学习相关的数学知识的。但是你也不必这样做(指学习数学知识),如果你真心讨厌数学的话。学习 Haskell 中例如函子和单子的抽象,就像你学习的 Java bean,Lua 的协程或 Scheme 的宏一样。事实上,函子(Functor)的思想是我最初学到的 关于计算机科学的内容之一 —— 它仅仅是你可以将一个函数映射在其上的任何类型。

函数式程序员必须非常聪明而且擅长计算机科学

无需多言,我是一个函数式程序员 :) 。

函数式编程不是黑暗、邪恶的魔法。(好吧,也许 Coq 是:P)。在许多方面,函数式编程实际上有助于你弥补并不强力的智商。Haskell 的类型系统极大地限制了你能写出的各种类型的错误功能,这使得你仅仅无脑利用类型系统就可以在困难的问题面前披荆斩棘。

我发现 Haskell 是进行编程喝酒7活动时最好的语言。所有愚蠢的错误 —— 以及一些不愚蠢的错误 —— 都可以被编译器捕捉到。所以我可以摆弄我的代码直到类型检查通过。然后,程序(通常)就可以正常工作了。通常到比其应有的(通过类型检查就能正常工作正常工作)的频率高得多。Haskell 给你提供诸多工具使你克服你自己的不靠谱。

函数式编程对初学者也是出奇地容易上手。例如,在 Jane Street 公司,他们为所有新进的交易员教授 OCaml 语言。我承认 Jane Steet 公司的交易员是特别聪明的一群人,但是他们中许多人绝不是程序员更不是计算机科学家。然而他们在短暂的 OCaml 集中培训后变得卓有效率,其中一些交易员花费了大量时间写函数式代码。

另一个很好的例子是 IMVU 公司。我的一个朋友在他们的团队,帮助相当数量的普通程序员迁移到 Haskell 上。对于 web 开发,同样地,人们能在短时间内变得有效率。冒着听起来傲慢自大的风险,像 IMVU 的公司中员工的平均智商水平比不上世界上顶级交易公司的员工。

也就是说,我的经验是 Haskell 社区中的人_确实_不成比例地聪明以及擅长计算机科学。并不是 Haskell 要求你如此,而是这个社区具有相当的自我选择特性。聪明的人似乎更可能主动选择 Haskell。实际上就像不久之前的 The Python Paradox,揭示了 Python 是如何被广泛接受为最易学的语言!


脚注4:

1这是一个很好的例子 ha ha only serious —— 有些最初看上去只是一个笑话,但实际上有深层的含义。Haskell 是一个很好的命令式语言,因为它拥有作为一等公民的“命令式动作”(“imperative actions”);你不必将语句封装在 lambda 中来将它们传来传去!这也使得将表示性的命令式控制结构,从这样的表示:

when

to

callCC

变成了库。

此外,Jargon File是令人难以置信的,如果你对计算机科学发展历程和“黑客”文化真正感兴趣,这将会是绝佳的资源。

2这是一段美妙的谈话。我爱他定义的概念框架,即便我不同意他的一些结论。静态类型 —— 尤其是基于 Hindley-Milner 且不带 subtyping 的 —— 实际上不复杂。其实在某些方面,它们比动态类型系统的 Clojure 使用简单!(译者注:竟然黑 Clojure!)

这里是一个谈话的链接:Simple Make Easy

3我坚持认为,这是成为有趣程序员简单而有趣的一个方式。

4还记得我提到在函数式编程中顺序并不重要?是的,这完全是我不能以一种合适方式记住脚注顺序的借口。毕竟,我内心深处还是一个函数式程序员!

5两类完全不同的模型(前面的是 5 系列,后面的是 7 系列),大多数编程语言都大体如此。

BMW-5-7

6实际上,就像函数式编程一样,FRP(函数反应式语言)已经存在一段时间了,至少从 1997 年开始。但它仅仅临近时日才开始抓住人们的目光。

7我喝酒时用过大量不同的语言,所以我可以做出公平比较。在旧金山的疯狂的世界中,我们通过解决 Project Euler 上的问题以及喝酒来寻乐子。多么美妙的生活啊!