为什么需要函数式编程?


非常对不起!!《你不懂JS》这个坑还没填完,这就开了个新坑……好吧,其实是因为我最近对函数式很感兴趣,但是又对 Haskell 之类的专业函数式语言感到有些畏惧,正巧就发现了这本书。看了大半之后觉得这书写的挺不错的,于是就决定翻译了……《你不懂JS》那边我不会弃坑的,不过速度会慢下来……

译者的前言

非常对不起!!《你不懂JS》这个坑还没填,这就开了个新坑……好吧,其实是因为我最近对函数式很感兴趣,但是又对Haskell之类的专业函数式语言感到有些畏惧,正巧就发现了这本书,本书正好是《你不懂JS》作者的新作。 本书名为Light,但其实并不是浅显易懂的函数是入门书,Light一词用作者的话来说,指的是有限的范围,但本书在它所提及到的几个主题里,绝对可以算是非常深入的。本书的一大特点是作者尽量减少了专业术语的使用和讲解,而将目光聚焦在了函数式的编程实践上。如果你准备深入学习JavaScript,如果你对函数式编程很感兴趣,那么本书非常适合你。 好了,闲话休谈,以下就正文!

前言

函数式程序员:(名词)命名变量”x”,命名函数”f”,然后命名代码模式”zygohistomorphic prepromorphism”

James Iry ‏@jamesiry 5/13/15
https://twitter.com/jamesiry/status/598547781515485184

函数式编程并不是一个新的概念,它几乎伴随着编程的整个历史。然而——我知道我这样说并不公平,但是!——它并不是过去几年在开发者的世界中的一个主流概念,我认为它更多的是在学术领域发光发热。 但是这一切都在改变,对FP感兴趣的人越来越多,在它周围已经形成了一种风潮,而且并不仅仅是在语言层面,还在于它的相关库和框架。你阅读这个文本的原因很可能就是因为你终于意识到FP并不是你能够忽略的东西。或者你也许也像我一样,试图学习了很多次函数式编程,但是总是在和各种术语和数学符号做斗争。 无论你因为什么来阅读这本书,欢迎来到宴会!

自信心

我有个非常简单的前提,那就是我作为软件开发(在JavaScript中)老师所做的一切:你不能信任任何你不明白的代码;此外,如果你不能信任或者理解你的代码,那么你可以不相信你编写的代码是适合你的业务的。你只能运行你的代码,然后祈求上天给你好运吧。 信任是什么意思?我的意思是你可以通过阅读来校阅你的代码,而不只是运行。你知道一段代码将会做什么,而不仅仅是依靠它应该做什么来验证。“也许”比“我们应该”使用的更频繁,我们倾向于依靠通过单元测试来验证我们程序的正确性。我并不是说单元测试是不好的,但是我认为,我们应该更深刻的理解我们所写的代码,这样在运行单元测试之前我们就能肯定它能够通过测试。 我相信构成函数式编程的基础技术来源于这样一种心态,那就是仅仅通过阅读代码就能让我们得到足够的自信心。对于理解了函数式编程,并且勤勉的在程序中运用它们的人,他们写下的代码是很容易阅读和验证的,通过那些他们已经证明过是正确的原则,得到他们想要的运行结果。 我希望从现在开始你将有更多的信心去编写你的代码,轻量级函数式编程原则将会指引你前往正确的方向。

交流

为什么函数式编程很重要?为了回答这个问题,我们需要先退一步,我们先来谈谈为什么编程本身很重要。 你听到这个可能会很惊讶,但是我并不认为代码是一组计算机的操作说明。事实上,我认为代码能够操作计算机几乎是个美丽的事故。

我深信,代码有一项更为重要的作用,那就是作为一种与其他人沟通的手段。

你好好回想一下你的经历吧,在你编码的绝大部分时间里,其实你都是在阅读现有的代码。花费所有或者是绝大部分时间来敲出所有全新的代码,而从来不去理别人的(或者我们自己过去)的代码,这是我们很少有的特权。 你知道吗,有一项研究表明,在维护代码的时候,其中70%的时间其实我们都是在阅读并理解它们。我觉得非常震惊,70%!难怪全球程序员平均每天的代码量是5行,然后我们还需要花费另外的7小时30分钟,来思考这5行代码应该写在哪里!

我想我们应该更多的去关注我们代码本身的可读性。顺带说一下,可读性可不是指最少的字符数。可读性很大程度上受到了熟悉程度的影响(是的,这玩意儿也被研究过)。 因此,如果我们要花更多的时间来让代码变得更加易读易懂,那么函数式编程就是个非常方便的模式。而函数式编程的规范已经进行过了深入的研究并最终确立了起来,它也被证明是可行的。 如果我们使用函数式编程的规范,我相信我们所编写的代码将会更容易理解。一旦我们理解这些规范,它们在代码里将会是熟悉和可识别的,意思就是说当我们阅读代码时我们将会花更少的时间去理解这部分的用意。我们的注意力将会转移到我们都很熟悉的部分,如何建立组装乐高积木,而不是乐高模块本身的意义。 函数式编程(至少,没有很多的专业术语让它更容易接受)是一种很高效的工具,用于编写可读性很高的代码,这也是它如此重要的原因。

可读性曲线

在这里我想特别提一件非常重要的事情,这件事情已经困扰我很多年了,并且在我写这本书的时候更为明显。 我想这可能也是很多开发者都有的倾向。你,亲爱的读者,通过这里的文章你可能也会发现你也在同样的沟里。放宽心,只要你坚持下去,曲线终究会回来的。

可读性曲线

我们将会在下一章详细的解释这个问题,对于命令式(Imperative)的代码而言,想必你已经写过很多了,比如if语句和for循环。它们擅长于精确的指导计算机如何去做某件事情。但是对于声明式(Declarative)的代码,以及我们马上就要开始努力学习的函数式的代码,它们更加专注于描述代码运行的结果。

让我告诉你一个痛苦的事实吧,我在编写这本书的时候,有件事情占据了我的绝大部分时间:我付出了很多很多的努力以及编写了很多很多代码,就为了能提高代码的可读性,并最大限度的减少或消除大多数你可能会写错的地方。 希望函数式编程的代码重构能够立即让你的代码变得更加优美优雅、简洁明快,这是不切实际的——至少在一开始这是不现实的。

对于思考如何结构化代码,使数据流更加明显,并帮助阅读你代码的人遵循你的想法,函数式编程是一种非常不同的方式。这个努力是非常值得的,但这也确实是个艰巨的旅程,你最终得到的代码可能看起来并不会具有更多的可读性,直到你花费了大量的时间来练习你的函数式编程。 此外,我的经验告诉我,在我足够理解某段代码的作用之前,我大约需要尝试6次,来把一个命令式的代码片段转换为声明式的函数代码。对我来说,编写函数式的代码更像是一个持续的过程,而不是一个范式到另一个范式的二元转换。 我常常使用“稍后阅读”的方法来测试我写的每一段函数式代码。就是说,我写完代码之后,然后把它晾在一旁几小时或者一整天,然后再尝试用全新的眼光来阅读它。通常而言,它都是非常混乱的,所以我又不断的对它进行调整。

函数式编程并没有让我变成高雅的艺术家,让我在画布上优雅的绘制让观众们都震惊不已的画作。相反,它是个艰苦、琐碎,有时还经常被忽略的杂草。 但是,我并没有挫败你的想法。我真的希望你能通过那些艰苦的过程,我非常高兴我已经完成了。我终于可以看到曲线向上弯曲的可能性,努力是值得的。我相信你也完全能做到这一点。

这本书将会给你的

我们将会从零开始学习函数式编程,揭示函数式编程最基本的原则,它们也都是函数式编程者们所做过的一切。但是在大多数情况下,我们将会远离那些骇人的术语和数学符号,因为它们总是很容易对初学者造成挫败感。 你如何去称呼那些东西并不重要,重要的是你明白它是什么以及它是如何工作的。这并不是说技术术语是不重要的——它毫无疑问地减轻了经验丰富的专业人士之间的沟通成本。但是对于初学者,我发现它可能会分散我们学习的注意力。 所以我希望这本书可以更多的关注基本概念,而不是那些花哨的术语。这并不是说完全没有术语,肯定是会有的,但是它们并不会被奇怪的单词包裹起来。越过他们直接看这些概念到底是什么,这就是这本书想要的。 我把这个系列称作“轻量级函数式编程”,这并不是个太正式的名称,因为我觉得如果你还不习惯于形式化的思考,那么函数式编程会是非常痛苦的,这也是真正的函数式编程形式主义令人诟病的地方。这并不是猜测,而是我自己个人化的感受。即使是在教授函数式编程以及写成这本书之后,函数式编程中的术语和符号的形式主义对于我来说仍然是非常不便的。我不断的进行着尝试,但是我似乎仍然没有获得成功。 我知道有相当的函数式编程者相信形式主义本身是有助于学习的。我觉得也只有当你正真习惯于形式化学习的时候才会这么认为,否则形式化对你而言就如同一道悬崖一般当在你面前。如果你已经有了数学或者CS的背景,这对你来说可能是很自然的,但是我们之中还有一些人并没有这样的背景,无论我们多么努力,形式主义都会成为学习的阻碍。 所以这本书介绍了我所认为的函数式编程建立起的概念,但是我会给你一个梯子让你去爬这个悬崖,而不是直接把你扔到悬崖面前,教导你让你自己去爬。

YAGNI

如果你已经从事编程很长时间了,你可能已经听到过短语“YAGNI”了,它是“你并不需要它(You Ain’t Gonna Need It)”的缩写。这一原则的主要来自极限编程,并强调了在确定需求之前就开始构建代码的高风险和高成本。 有时我们会猜测我们在未来可能会需要的某些功能,因为我们现在正在构建着别的东西,所以理所当然般的觉得这个功能现在实现起来应该会更容易。然后我们猜错了,我们并不需要这个功能,或者说我们需要的东西完全不同。有时我们也会猜对,但是过早的构建一个需求,会挤占我们花在真正需要的需求中的时间,这会提高我们的机会成本以及浪费我们的能量。 YAGNI原则就是说:不管是多么反直觉的情况,我们应当推迟某些需求建立的时间,直到它真正被需要。我们通常都倾向于夸大我们对未来重构成本的心理预估,以便于我们在需要的时候添加它;不过奇怪的是,在完成它之后我们又多半会认为它并不是之前以为的那么困难。 由于它只适用于函数式编程,我想要在这里提醒各位读者:在本书中你将会看到很多有趣和引人注目的设计模式,但是着仅仅只是这些设计模式的非常奇特的应用而已,给出的这些代码很可能并不一定是适当的。 在很多情况下你可以使用函数式编程,但并不意味着你必须使用函数式编程。这也是我同许多科班出身的函数式程序员们不同的地方。此外,我们有很多种方法来分割问题,即便您可能已经学习了一种更为复杂的方法,还具有着“面向未来”的可维护性和可扩展性,与此相比,更为简单的函数式编程模式在这一点上是足够优秀的。

一般来说,我建议你在编写的代码的时候平衡一点更好,并且在你接受函数式编程的概念之后仍然适当的保守一点。一个范式或模型,究竟会使得这部分代码更易读,还是仅引入了一个尚未验证、但更加明智的技巧而已,可以默认使用YAGNI原则来判断。

记住,任何从未使用过的扩展点不仅仅是在浪费你的努力,它也同样在阻挡着你前进。

Jeremy D. Miller @jeremydmiller 2/20/15 https://twitter.com/jeremydmiller/status/568797862441586688

记住,你所写下的每一行代码都连带着读者的阅读成本。那个读者可能是另一个团队的成员,甚至是未来的你自己。对于这些读者而言,你那炫技般过于巧妙和复杂的函数式代码片段并不会让它们觉得印象深刻。 最好的代码是即便在未来也拥有着极高的可行性的代码,因为它准确的处在它到底能/应该是什么(理想)以及它必须是什么(实用主义)之间的平衡点之上。

资源

我在写这篇文章的时候已经参考了很多不同的资源,我相信你也可以从他们之中受益,所以我想花点时间将它们指出来。

这些JavaScript函数式编程的书籍,你一定要去读读看:

博客/网站

你可以参考看看的其他作者和文章:

本书中的所有代码片段都不会使用库,对于我们发现的每个操作,我们都会演示如何在独立、纯粹的JavaScript中实现他。然而,当你开始使用函数式构建更多真正的代码的时候,你很快就会想要一个库,来提供这些实用程序的更为优化更为可靠的版本。 顺便说一下,你需要确实地检查库的函数库文档,以确保你知道它们是如何工作的。在其中许多代码之中,我们将会有很多相似之处,但是毫无疑问的,即使是在流行的库中,也是有些差异的。 这里有些流行的JavaScript函数式库,这是你开启伟大征途的地方:

在附录C中,你将会看到这些库的简单使用说明。

总结

这就是JavaScript轻量级函数式编程。我们的目标是学习如何与代码沟通,而并不是被术语和符号所压倒。我希望这本书能够开启你的旅程!