diff --git a/pragmatic-functional-programming/README.md b/pragmatic-functional-programming/README.md index fb12c88..9d20dc5 100644 --- a/pragmatic-functional-programming/README.md +++ b/pragmatic-functional-programming/README.md @@ -3,45 +3,51 @@ # 务实的函数式编程 -函数式编程(`functional programming`)正式开始有长足的发展始于10年前,从那时起,我开始看到`Scala`、`Clojure`和`F#`这样的语言得到关注。这种关注并非只是像『哇,一个新语言,酷!』这样短暂的热度,而是确实有某些实在的原因在推动着它 —— 或者至少我们是这么认为的。 +函数式编程(`functional programming`/`FP`)的风潮,认真地来说是从大概10年前开始。我们开始关注像`Scala`、`Clojure`和`F#`这样的语言。这个风潮并非只是平平常常的『哇酷~一个新语言!』这样的热情。确实有某些实在的原因在推动着 —— 或者我们是这么想的。 -摩尔定律告诉我们每隔18个月,计算机的速度就会翻倍。这个定律一直从1960和2000都始终有效。但是随后,它开始失效,慢慢冷却下来。时钟频率到达3 GHZ以后,达到了一个瓶颈期。我们已经走到了光速的限制。信号不能在芯片表面以更高的速度快速传播。 +摩尔定律告诉过我们,每隔18个月计算机的速度就会翻倍。这个定律从60年代和2000年都是有效的。但是之后失效了。大家冷静想一下。时钟频率到达3G HZ以后不再变快进入平台期了。这已经触到了光速的物理极限,在芯片上的信号传播已经快到不能有再快了。 -所以硬件设计者改变了策略。为了获得更大的吞吐量,他们添加了更多的处理器(核心数)。同时为了这些核腾出空间,他们从芯片上移除了很多缓存(`cacheing`)和管道(`pipelining`)硬件。因而,处理器的确比之前慢了一点,但是由于有了更多的处理器,吞吐量仍然得到了增长。 +所以硬件设计者改变了策略。为了获得更大的吞吐量,他们添加了更多的处理器(核心数)。同时为了这些核腾出空间,他们从芯片上去掉了很多缓存(`cacheing`)和流水线(`pipelining`)硬件。因而,单个处理器是比之前的要慢一些的;但是由于有了更多的处理器,吞吐量仍然是提升的。 -8年前,我有了第一台双核机器。两年后我有了一个4核的机器。这些核心数已经开始不断增长。那个时候我们都相信,它将会以我们无法想象的方式影响软件发展。 +> 【译注】:关于流水线(`pipeline`)参见[指令流水线(`instruction pipeline`) - zh.wikipedia.org](https://zh.wikipedia.org/wiki/%E6%8C%87%E4%BB%A4%E7%AE%A1%E7%B7%9A%E5%8C%96)。 -于是我们开始学习函数式编程(`FP`)。一旦变量被初始化后,函数式编程强烈不支持再对变量的状态进行改变。这对并发(`concurrency`)有着深远的影响。如果你无法改变一个变量的状态,就不会有一个竞争条件(`race condition`)。如果你更新一个变量的值,也不会有并发更新的问题。 +8年前我有了第一台双核机器,两年后有了一台4核的机器。也就是说,核心数开始进入了快速增加时期。那时候我们都认识到,这将会以我们无法想象的方式影响软件开发。 -当然了,这曾经被认为是多核问题的解决方案。当核心数激增,并发,不止!**共时性**(`simultaneity`)将会成为一个非常显著的问题。函数式编程应该提供一个编程方式,这种方式会减轻在单个处理器应对1024核可能会出现的问题。 +一个对策就是学习`FP`。`FP`会极力避免在变量初始化之后改变对其状态(`state`)。这对并发(`concurrency`)有着深远的影响。如果不能改变变量的状态,就不会有竞争条件(`race condition`)。如果不能更新变量的值(`value`),也不会有并发更新(`concurrent update`)的问题。 -所以,所有人开始学习`Clojure`、`Scala`、`F#`或是`Haskell`;因为他们相信函数式编程终会大放异彩,他们想要提前为这一天做好准备。 +当然了,这曾被认为是多核问题的解决方案。当核心数激增,并发,甚至是**共时性**(`simultaneity`),都会成为一个大问题。`FP`可以说是提供一个编程风格(`the programming style`),可以减轻在单个处理器应对1024核时会出现的问题。 -然而,这一天终究没有到来。六年前我有了一个4核的笔记本,然后我又有了两个4核。而我的下一台笔记本估计也是4核。我们又到了另一个瓶颈期? +所以所有人都开始学习`Clojure`、`Scala`、`F#`或是`Haskell`;因为大家相信`FP`的冲锋号已经吹响,都想作好准备! -> 说个题外话,昨晚我看了一部2007年的电影。女主角正在使用一个笔记本,使用`Google`在一个时髦的浏览器里面浏览网页,使用翻盖手机接收信息。一切是那么熟悉。不过这已经过时了 —— 我可以看出笔记本的模型老旧,浏览器是个老版本,翻盖手机与今天的智能手机也实在是相差甚远。然而 —— 这种变化并没有从2000到2011年的那般戏剧化,也没有从1990到2000年的翻天覆地。我们又到了在计算机和软件技术上的一个瓶颈期了吗? +然而,这一天一直没有到来。六年前我有了一个4核的笔记本,然后我又有了两个4核。而我的下一台笔记本估计也是4核。我们又进入了另一个平台期了? -所以,也许函数式编程并不想我们曾经想象的那么重要。或许我们不会被那么多的核心包围,也不用去担心在芯片上有32768个核心。或许我们都可以放松一下,回到之前更新变量的时候。 +> 说个题外话,昨晚我看了一部2007年的电影。女主角在用笔记本,在一个时髦的浏览器里面浏览网页,在使用`Google`,以及用翻盖手机收发短信。一切都是那么的熟悉。只不过都过时了 —— 我可以看出笔记本是老型号,浏览器是个老版本,而翻盖手机比起今天的智能手机就像个古董。而这些方面是在2000到2011年之后有了翻天覆地的变化,从1990到2000年才有变化。我们真的能预见在计算机和软件技术上现在是一个平台期吗? -不过,我认为这将会是一个重大的错误,跟滥用`goto`一样严重的错误。和放弃动态调度(`dynamic dispatch`)一样危险。 +**_ TODO _**上面的这句比较级要好好推敲! -为什么呢?从一开始让我们感兴趣的地方开始 —— 函数式编程使得并发变得十分容易。如果你要搭建一个有很多线程或是进程的系统,使用函数式编程将会大大减少你可能由于竞争条件和并发更新遇到的问题。 +所以,或许,`FP`并不像我们之前想的那样是关键的一个技能。或许,我们不会被那么多的核包围。或许,也不用去担心在芯片上有32768个核。或许,我们都可以放松一下,回到以前那样去更新变量。 -还有呢?函数式编程更易写、易读、易于测试和理解。听到这些,相信很多人已经开始兴奋了。当尝试过函数式编程以后,你会发现一切都非常容易。所有的`map`、`reduce`和递归 —— 尤其是**尾递归**,都非常简单。使用这些只是一个熟悉程度的问题。一旦你熟悉这些概念以后 —— 并不会花费太长时间,编程会变得**容易的多**。 +但是,我觉得这会是一个错误,一个严重的错误。我觉得这会和以前滥用`goto`一样严重。我觉得这会和放弃动态派发(`dynamic dispatch`)一样危险。 -为什么变得容易了呢?因为你不再需要跟踪系统的状态。由于变量的状态无法改变,所以系统的状态也就维持不变。不需要跟踪的不仅仅是系统,列表、集合、栈、队列等通通都不需要再进行跟踪,因为这些数据结构也无法改变。在一个函数式编程语言中,当你向一个栈`push`一个元素,你将会得到一个新的栈,原来的栈并不会发生改变。这意味着减轻了程序员的负担,他们所需要记忆的东西更少了,需要跟踪的东西更少了。因而,代码会更易写、易读、易于理解和测试。 +> 【译注】:关于动态派发(`dynamic dispatch`)参见[动态调度(`dynamic dispatch`) - zh.wikipedia.org](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%B0%83%E5%BA%A6)。 -那么,你应该使用哪种函数式编程语言呢?我最喜欢的是`Clojure`。因为`Clojure`极其简单。它是`Lisp`的一个方言,`Lisp`是一个十分简单和漂亮的语言。在这里,来稍微展示一下: +为什么呢?让我们回到起初引起我们兴趣的原因 —— `FP`使得并发变得安全得多。如果你要搭建一个有很多线程或是进程的系统,使用`FP`会大大减少可能由竞争条件和并发更新引发的问题。 -在`Java`中的一个函数:`f(x)`; +还有其它理由吗?呃~`FP`更易写、易读、易于测试和易于理解。听到这些,我能想象到,有些人已经在挥舞着拳头和抛桌子了。当你尝试过`FP`,你发现一点也不容易。`map`、`reduce`和递归 —— 尤其是**尾递归**,有哪一个是容易的?是的,收到收到。其实使用这些是一个熟悉程度的问题。一旦你熟悉这些概念以后(这个熟悉过程其实并不需要太长时间),编程就会变得**容易的多**。 -现在,将它转换为`Lisp`的一个函数,简单地将第一个括号移到左边即可:`(f x)`。 +为什么会变得容易得多呢?因为你不再需要跟踪系统的状态。由于变量的状态无法改变,所以系统的状态也就维持不变。不需要跟踪的不仅仅是系统,还有列表、集合、栈、队列等等通通都不需要跟踪状态,因为这些数据结构也无法改变。在`FP`语言中,当你向一个栈`push`一个元素,你将会得到一个新的栈,并不会改变原来的栈。这意味着减轻了程序员的负担,他们所需要记忆的东西更少了。需要跟踪的东西更少了,因而代码变的编写、阅读读、理解和测试。 -现在,你已经学会95%的`Lisp`和90%的`Clojure`了。对这些语言而言,这些括号就是全部的语法了。**极其**简单。 +那么,你应该使用哪种`FP`语言呢?我最喜欢的是`Clojure`。因为`Clojure`极其简单。它是`Lisp`的一个方言,`Lisp`是一个简单和漂亮的语言。让我给你展示一下: + +在`Java`中的函数:`f(x)`; + +转换成`Lisp`的函数就是简单地将第一个括号移到左边即可:`(f x)`。 + +现在,你已经学会95%的`Lisp`和90%的`Clojure`了。对这些语言而言,这些小括号就是全部的语法了。**极其**简单。 你可能以前见过`Lisp`程序,不过不喜欢这些括号。可能你也不喜欢`CAR`、`CDR`和`CADR`这些。别担心。`Clojure`有着比`Lisp`更多的符号,所以括号相对少一些。`Clojure`用`first`、`rest`和`second`代替了`CAR`、`CDR`和`CADR`。此外,`Clojure`基于`JVM`,它完全可以访问`Java`库,和任何其他的`Java`框架和库。它的互用性快速而便捷。更好的一点是,`Clojure`能够拥有`JVM`完全的面向对象特征。 -『等一下!』你可能会说,『函数式编程和面对对象是相互不兼容的!』谁告诉你的?事实并非如此!在函数式编程中,你的确无法改变一个对象的状态。但是那又怎么样呢?当你想要对一个对象进行改变时,得到一个新的对象就好了,之前的对象无须改变。一旦你习惯于此,这是十分容易处理的。 +『等一下!』你可能会说,『`FP`和面对对象是相互不兼容的!』谁告诉你的?事实并非如此!在`FP`中,你的确无法改变一个对象的状态。但是那又怎么样呢?当你想要对一个对象进行改变时,得到一个新的对象就好了,之前的对象无须改变。一旦你习惯于此,这是十分容易处理的。 再回到面向对象。我发现面向对象最有用的一个特性是,在软件架构层面的动态多态性。`Clojure`提供了对`Java`动态多态性的完全接入。最好是用例子解释一下: @@ -76,5 +82,5 @@ public interface Gateway { 跟`Lisp`一样,`Clojure`也是一个**同像性**(`Homoiconic`)的语言,也就是说,代码本身就是程序能够操作的数据。这不难看出。下面的代码:`(1 2 3)` 表示一个三个整数的列表(`list`)。如果该列表的第一个元素变成了一个函数,也就是`(f 2 3)`,那么它就变成了一个函数调用。故而,在`Clojure`中,所有的函数调用都是列表。列表可以直接被代码操作。所以,一个程序也可以构造和执行其他程序。 -最后说一句,函数式编程十分重要。你应该去学习它。如果你还在想你应该从哪个语言学起,我推荐`Clojure`。 +最后说一句,`FP`十分重要。你应该去学习它。如果你还在想你应该从哪个语言学起,我推荐`Clojure`。