函数式编程,我们这次赶上了吗?

公元1945年,Samuel Eilenberg和Mac Lane一同建立了范畴论,当时他们只把这个不怎么起眼的东西当作工具来解决一点代数拓扑上的问题。而这个东西是如此抽象和玄幻,以致于在之后很长一段时间内范畴论都在被当作哲学来讨论,直到一个叫格罗滕迪克的年轻人突发奇想把它真正用到数学研究上去,这个后来被封为数学之神的人在那一瞬间启发了凡人数学家们,于是范畴论开始从一门哲学变成了被数学家认真对待的东西。纯数学有太多的东西无法进入应用领域而被人类所关注了,所以绝大部分数学家可能都没有想到,这套玄门秘术会在计算机应用领域大放光彩。

同年8月,日本接受《波茨坦公告》,人类历史上死伤人数最多的全球暴力事件终于离划上句号只差一个签字了。谁也没有想到,人类历史的一次大波折刚刚落下帷幕,科技史上的一次大冲击又在酝酿之中了。

同月74年之后,我下了出租车,站在柏林波茨坦广场,身边就是柏林墙。望着晚霞中的斯坎迪娅酒店,璞玉楼台浑色金。人间此会论今古,心里想的却不是即将开幕的ICFP以及十几小时之后要做的演讲。我想到了一个很关键的问题,那就是这里是繁华商业所占据的波茨坦广场,而不是历史遗迹覆盖的波茨坦。波茨坦广场和波茨坦之间的关系,就是JavaScript和Java之间的关系,等价于雷锋叔叔和雷峰塔的关系。我终究没有机会在这里缅怀过去,因为我来到这里,是面向未来的。

编程就编程,为什么要追求优雅?

孔子说,里仁为美。意思是定居要选个好地方,怎么个叫好地方?就是这个地方的人要知道仁义廉耻,不然住在这里智商会降低。择不处仁,焉得知(zhì,通“智”)?

我看柏林是个好地方,春风日日过东墙,过得多了就把柏林墙吹塌了。既然日过了东墙,肯定是要增添些智商的。既然有了智商,当然也就有了自信,要写个文章解释一下什么是编程语言的优雅。

关于优雅,英语里大致有两类词可用:

一种类似俚语posh,大意是说一个人喜欢把一个事情做花哨点(fancy),把自己用某种时尚“包装起来”;

另一类比如grace,用来形容一个人有内而外的得体,如果有朝一日真的“到那个份上了”,不仅内涵得体而且有身份了,就可以用elegant来形容这种人。

很明显,一个人即便一开始是posh,也完全可能通过某种路径最终走向elegant的。也就是英语里说的fake it till make,一开始只是装逼,但是随着见识的增加,耳濡目染,凭借自身的努力,逐渐就真的成为了心目中那样的人。

那么从范畴论的角度来看,我们来给一群人贴标签,我听说如今特别流行给人贴标签。把人群作为一个范畴,如果你造了间屋子,把进入屋子的人都贴上一个posh标签,那么这间屋子就称为一个posh Functor,类似可得elegant Functor。但是这群posh的人又可以通过fake it till make的方式成为elegant那类人,那么这个fake it till make的办法就叫做natural transformation。这些就是现在函数式编程最基本的一些概念,其实都很简单,人人都能学得会。

而汉语里面谈优雅,“是否得体”只不过是一半涵义而已,另一半则是“含蓄”。含蓄有什么好处,含蓄需要去做abstract。把所要表达的东西用更高的抽象层次表示出来,其表达性要远高于直接陈述。

良好的编程风格,讲究细节的隐藏,只暴露其语义。这样阅读代码的时候就可以快速抓住其在上下文中的涵义,而无需计较细节。

另一方面,当需要计较细节的时候,比如debug,实现细节已经被封装在起来了,那么排错的时候范围就被缩小了,可以提高debug效率。

前几天还有人在reddit上跟我抬杠,说C++明明可以用迭代语法,不明白为什么非要用Functor,言下之意似乎是我还不熟悉语法。我就跟这个家伙解释,如果用迭代语法不仅细节暴露了,而且是个statement不能当作参数传入,那就不能跟其他的函数进行组合;而用Functor就可以跟其他Functor进行组合,以这样简洁的方式这样就可以构建更大的更复杂的程序。不知道这家伙听懂没有,我看很多人都懂了,这应该也不难理解。

但是这一切都有一个范围,那就是不能是太小而功能简单的程序,因为那样的程序实在犯不着这么讲究,除非作为一种练习。

所以优雅地编程,就是要有这样的得体,以及善于隐藏细节,将功能性的东西抽象出来,让一个复杂的程序看上去就像是一系列功能的组合。这说起来简单,但是由于工程上存在的各种复杂性,尤其是副作用导致的复杂性,让这种“无非是把各种功能组合起来”的方法论成了一种奢侈的追求。大道虽简,但是行走这条大道却充满了荆棘。于是就有了聪明人试图发明一些工具,让你行走这条大道的时候没那么痛苦,函数式语言就在这种需求下应运而生。

然而对于真正的工程来说,换语言不是一件容易的事,好在函数式编程也可以作为一种范式而存在,只要你所使用的编程语言支持一些最基本的特性,如闭包(或lambda)、尾递归等,就可以将函数式编程的范式融入你的日常开发之中。虽然有些时候相比真正的函数式语言来说有些蹩脚,但是工程不是艺术,工程以解决实际问题为目标,这暂时也就够了。

避免过度优雅

在英文写作中,有一个术语叫做elegant variation,形容那些说毫无必要地使用一些替代性描述的写法,虽然表达的意思相同,但刻意地不使用更为清晰地表述,反而造成了阅读困难。甚至看似暗含了些什么意思,当读者试图深挖的时候,发现其实什么意思都没有,造成了一种阅读体验上的缺憾。

比如本文上一节开头的几句话,就属于elegant variation,其实我想表达的意思就是柏林天气很好,没什么别的意思。

那么我们在追求编程优雅性的过程中,也要注意避免这类情形。

比如写个简单独立的web服务器就不要去构造Functor了,while循环也是有尊严的。实际上不仅是函数式编程,以前面向对象流行的时候,也有年轻人不分青红皂白上来就用设计模式。我再说一次,while循环也是有尊严的,大到一棵树,小到一个指针,都有其尊严,尊重它们就是尊重你的时间。

但是如果你的程序是一个大系统的某部分,哪怕它再小再简单,都是值得琢磨让它变得优雅的。因为你肯定不希望这一辈子都维护这个程序,将来让人接手以后也希望彻底脱手。说白了,尊重别人的时间就是尊重自己的时间。

当然,尽信书不如无书。一个人即便通读各种秘籍,把各种编程范式玩得天花乱坠,也不如认认真真写文档的人。要我说,最优雅的编程范式,就是好好写文档和注释。文档和注释是不一样的,前者是写给不熟悉程序内部结构的使用者看,后者是写给合作者看的,这其中差别值得另写一篇文章了。

函数式编程 != Haskell

在过去一提起函数式编程,许多人的第一个反应是Lisp或者Scheme。时代在发展,如今一提函数式编程,许多人第一个反应是Haskell。

有人在Quora上问这年头SICP是否还有价值作为FP的教材来学习,答案其实是肯定的,因为Haskell所展现的是函数式编程更为现代的东西,Functor、Applicative、Composition、Monad之类,而SICP展现的是经典的东西,闭包、高阶过程、lazy等基础东西,一脉相承的,你在Haskell里这些经典东西一样要用。

某种程度上这很合理,因为现代函数式编程主要是在范畴论里打转,而Haskell是针对范畴论设计的编程语言,这使得它在表达范畴论的东西时特别简洁优雅。

想要用比较传统的语言,比如C++来实现同样效果会比较蛋疼,但用肯定是没问题的,有兴趣可以读 8 essential patterns you should know about functional programming in C++14.

但是问题还是在于,换语言对实际工程来说不是一件容易的事。几年前碰到个老友,他刚跳槽去了某个还蛮有名的硅谷互联网公司,然后热情地跟我大肆介绍那边Haskell氛围有多么浓厚,于是我就问实际生产里有没有用,他犹豫了下说生产中实际还是用Javascript,更多地还是从Haskell里学习范式。这个嘛,就比较符合常理了。

不过这也是好几年前的事了,现在是不是切换到了Haskell也难说。毕竟当你特别熟悉范式的时候,会更希望直接切到更符合该范式设计的语言。所以在我看来,先从范式学习和实践入手,会是一个比较理性的方法论。

为什么我要强调函数式编程并不等于Haskell?我看很多人在用别的语言模仿这些范式的时候,有意无意地去套用Haskell语法,这是毫无必要的,某种程度上讲,这其实是没有理解本质,就是去套别的语言的语法而已。套出来很难看,而且也不懂为什么要这样套。比如Haskell里告诉你Array是个Monad,那是因为Haskell里的Array是个ADT,它被优雅地实现成了一个Monad,不是说Array就一定是Monad。

Haskell虽好,但它在学习上确实带来了一些问题,那就是它所展现的优雅只属于它自己,没有办法转移到别的语言上。然而如果根据范畴论的原理来做,在别的语言上自有别的做法,无需套用Haskell的语法,那样会好很多。

产业中的实际应用

这些年函数式编程的思想深入到了IT产业的许多方面,最有名的当属谷歌的MapReduce,还有现在很火的亚马逊lambda架构,还有不知道还能活几年的docker(如果你熟悉Scheme,就会明白docker的本质就是个超大型的continuation)。其实还有很多方面值得一提,尤其是形式化验证方面,在区块链和芯片设计上都是老生常谈。连硬件设计方面也开始转向函数式编程,可以忘掉verilog和VHDL了,未来FPGA可能都是函数式语言开发了,其中包括基于Scala方言开发的Chisle语言

当然,如今年轻人更为关注的可能是比较烂俗的deep learning之类,那么除了Python以外,函数式编程领域是否有相关的东西呢?首先我跟大家汇报一下,Haskell那个tensorflow我是没跑起来。但是如果你想用Scheme或者Lua来用tensorflow,未来可以考虑AIScm,但是AIScm只是绑定了tensorflow(和一大堆视觉相关的库),我肯定是跑起来了的,但是接口太low-level,我们在基于它写一个类似Keras的东西。没办法,虽然deep learning连扫地大妈都能聊几句,但是我们需要在产品里加个推荐系统,这年头不用deep learning也不好,如果未来能去掉Python,利用AIScm基于LLVM-jit的优化,那就更好了。

总体上来说,未来还是比较值得期待的,如果你身边的人还没有听说过函数式编程,欢迎介绍他们阅读我的文章。

最后,这趟柏林之旅怎么样

如果要吹牛逼聊八卦,料就很多了,以后有的是时间。现在唯一想说的就是,柏林天气很好,就这样。