对象-函数式编程简史

本文是一篇风格轻松的概述Scala语言诞生过程中的各种软件开发运动历史事件的文章。

前言

从前,有一种编程语言叫Scala。

人们研究这种语言,发现这是一种“给人印象深刻”的语言,但是由于这种语言的功能特征不断的急速进化,导致除了一些自己研究的项目外,没有其他人再使用这种语言开发了。
这种语言看起来很美,但没有人愿意冒险把自己的职业生涯依赖于这种语言上,这个语言太年轻了,谁能保证它不会夭折?

之后,发生了一些事情; Scala 长大了。 Twitter 宣布他们用Scala语言替换了以前一些用Ruby开发的后端程序,而SAP也在使用这种语言,还有EDF等。 这消息迅速传播开来,有许多新的程序开发者慕名而来,他们也都感觉到这是一种“令人印象深刻”的语言,同时,早期的这个语言的信徒也开始发现此语言已经凤凰涅磐,让他们眼睛一亮。

他们现在看到的这种语言已经是一个成熟的、急不可待的等人们使用它去大展宏图的语言了。 随着2.8版本的发布,Scala 终于从少年进入了青年,可以当之无愧的接受“令人印象深刻”的赞誉了。

编程就是人生

程序语言在进化,在繁衍,产生不同的种族。 非常类似于生命在早期地球的上的构成,编程语言最初是诞生于由CPU指令和数学概念混成的沸腾的高汤里。 跟生命的发展不同的是,它们不需要泥土。

但是它们也经历着残酷血腥的优胜劣汰、物竞天择过程,当然,你可以把它们之间的战争想像成关于Tab键和Space键,关于括弧在程序中的地位问题上的战争 。
人们就像一个优秀的饲养员只喜欢挑选一个纯种血统的良马一样选择自己喜爱的编程语言。 就像生物学上,人工育种必然存在不足,近亲连续不断地繁殖、以此来保存某一血统的令人满意的特性的做法必然潜藏基因缺陷的危险。
幸运的事,凡事都有两面, 好的饲养人和园丁会选择利用 混血杂交优势.
父母的结合产生的后代汇聚了其父母双方各自不同的特征,所以后代比前代更强大,同时父母各自的弱点也会被后代查明从而摒弃。 同样的思想也被应用到了编程语言的世界里[2],各种面向对象和面向函数风格的概念相互融合给予了程序员们前所未有的能力和表达方式。

Scala编程语言就是这样的语言中的一员。


[1] To the best of my knowledge, although there was a bit of dust.
[2] Memes can also evolve…

言归正传!

我估计阅读我这篇文章的大部分是Java程序员,所以在我详细的解释函数和对象如何交互之前我打算先介绍一些关于针对函数编程的概念。

其实在网上已经有了很多完全超出我的写作水平的好教材,所以我愿意尽量简单的介绍一下。

什么是函数?

数学里,函数就是接受一个值(输入值)而后使用它产生另外一个值(输出)的运算。 在很长的时间里这个定义几乎适用有所有任何的情况,即使是现在,数学家们也只是在扩充这个定义里的“值”的概念: 复杂数值,矩阵,向量,坐标(对称坐标和笛卡尔坐标),四元数。.. 很多东西都可以被当作“值”,只要你用正确的方式去看待它。

这种情况持续了很久,之后程序员出现了,之后计算机被发明了。
一旦人们对计算机技术的重要性达成共识,并且使计算机技术逐步完善起来,程序员就开始用一种新的思想考虑他们了,比如:看着这计算机打印输出的长河般一排排的三个字母组成的汇编程序码,你不头痛也不行。

如果他们能把那些序列码按相同的功能分成一组一组,给它们起个名称,那么他们将会有一种简洁的方式去重复利用这些代码,那么以前花大量时间拼写这些代码的时间节省下来,终于有了去酒馆的时间。 因为很多的程序员也都是数学家,因为很多他们的程序都是用来解决数学问题的,这就决定了函数的概念非常简洁的迎合了这种给编程单元打包处理的行为,从此第二代编程语言诞生了。


[3] Yes, really. Ada Lovelace was doing her thing before Babbage assembled so much as a single gear.

完美中的不足…

这种革新,完美中有些不足。 针对函数,人们发现一个问题,就是经常需要它们一次处理多个输入值,或,更令人沮丧的,多个输出值。 幸运的是一些数学家解决了如果让函数处理多个输入值的问题,这种思想很早就被人采纳了,人们按照这种思路想出来如何去返回多个输出值(通常是把输入值给抹去,替换我想要输出的值)。 但是其他的一些数学家(例如Haskell)并不喜欢多个输入值的方式,他产生了一个新的观点,用高阶函数替代多个输入值,函数可以返回其它函数,或可以用函数体当作函数参数,但这种做法很难实现,所以程序员起初都没在意这种观点。

函数编程还有一个问题,就是它有副作用。 一个函数使用一个相同输入值(例如读一个文件)却可以每次都做出不同的事情,或者它可以去做一些不专一的事情(例如处理返回一个值外还会向控制台打印一行字)。 更糟糕的是,它会把自己的输入值在使用之后改变其值! 对于那些想利用这些副作用的人来说,这是再好不过了,可是对于另外的一些数学家就不一样了,他们不喜欢喝啤酒,可是还必须要把啤酒杯拿在手上。

所以程序里的函数跟数学里的函数是不同的。 人们给出了一个新的定义(不是很精确的):一个程序,或者一批指令,具有一个名称,可以选择性的拥有一个或多个输入值和输出值,甚至同时具备多个输入值和多个输出值,同时还能做点额外的事情。

A reprive ahead of its time

自然,很多数学家并不高兴函数被定义成这样,于是一个新的语言品种被创造了出来,用来弥补其先天的不足,再一次的将它用一个稳固的理论架构确定下来。

函数体成为第一类实体,而非以前的仅是一批代码的别名。 这样Haskell的高阶函数的概念就可以应用于设计开发软件了。 编程语言的进化发展中人们越来越多的鼓励使用常量值,这样函数就不能把输入值给能脏了。 人们实现了局部套用(Currying),开始使用数组结构,这样函数终于又回到了只能接受一个输入值和一个输出值的绅士面貌。 一些有趣的方法被人们采用来限制那些讨厌的”副作用“:如果这些副作用不能完全避免的话,那就把它们规整起来专门找个地方放置它们。 这样的语系被人们称作为“函数式”编程语言,因为它们把函数的概念回归到了其数学上的根源。 这个语系里的语言包括有Lisp, Scheme, Caml, Erlang, F#, Clojure等。

作为工程学上一个优秀的典范,函数式语言具有设计优良,易理解,高效,结构稳定等优点。 与此同时,如同其他Good Ideas?经常遇到的情况一样,很长的一段时间里它们被主流团体所遗忘。 程序员们都很清楚为什么人们喜欢把函数放在首要位置; 人们需要把系统按单元功能划分,相互不依赖,可以在不同的地方重复使用它们。 这些愿望就像痒痒需要挠的感觉折磨着人们,于是面向对象的思维从此诞生了并崛起了。
目前,函数式编程只是被人们当成一种业余爱好,也被人们用在相关的演讲和论文里去灵巧的阐述一些新事物。 人们通常认为函数式语言会比命令式语言运行的慢,但这种结论也许只有上帝知道,因为从来没有人用自己的方式证明过。 人们还认为,尽管函数式语言看起来非常简洁,适合小的程序和做演示用,但它们不太适合大规模的程序,像那些成百上千行的程序,如果用函数式语言来开发,几乎是不可维护的。


[4] Often using monads
[5] Except, maybe, for (all(the(parenthesis())))
[6] at least, those who wanted to be pragmatic so they could get more beer time
[7] But that’s a different story…

重生

实际上,函数式语言并不只是一种玩物。 跟随着时代革新的大潮,它在地下酝酿了这么多年,终于等到了这个世界可是接受它的这一天。 主流程序员们越来越多的认识到,函数式语言是如此的容易使用,而这一点在其它(面向对象)代码是难以达到的。 就比如这个简单的问题“处理这个字符串队列,将它们全部转化成大写后返回”,用Java编写却有可能出错。 因为偶尔人们会忽略掉这个队列里的第一个字串,因为他们从1开始计数,而不是0。有时候人们会发现这个队列里的字串不是按他们要求的部分转化成大写,而是全部大写了,还有些时候程序会报出空指针异常。

逐渐的,人们开始讨论起closures和continuations,为的是让他们的程序更加的强壮和可维护。 当时这些东西并不是对象们所能具有的,于是加强型for循环被发明了,还有匿名类,visitor模式,command 模式。 当然这些没有一个能按照程序员们想象的那样的完美,但这些东西还是有用的,让很多有问题的地方变得可维护了(即使这样需要编排一些丑陋的模板式的代码)。 时机已经到了人们改变思维方式的时候了,函数式语言已经迫不及待的看到自己的宏大入场了。


[8] Though not the null pointer exceptions

让人嫉妒的特性

通过Erlang语言,爱立信演示了函数式语言如何能应用于大规模系统的。 而其开发效率高,可维护性,可测试性都很好,特别是不易犯错。 这才是真正的函数式语言的面貌,感觉比面向对象语言要成功的多。 爱立信的程序员们前所未有的有了充分的喝啤酒的空闲时间了。 生活变得轻松起来!

而在另外的阵营里的程序员看待函数式语言有点想法,也有的嫉妒。 Java变得如此臃肿,而且,每一个新出现的特征都看起来是围绕着它的模板代码风格创造出来的。 即使是很小的程序,现在也要使用annotations,模板参数,和duplicate type declarations,大程序问题就更大了。 不幸中的不幸,关于如何往Java里添加closures(闭包)功能的讨论并不像早期预期的那样顺利,还有,Java bean里的数不完的get/set方法实在是不能在忍受了。
有些事情必须要变了。

除了这些,Java还有一大堆的问题。 The Virtual Machine(虚拟机)是一个非常成熟的工具,经过了很好的优化,市场上随处可见,从洗衣机,移动电话,到数不清的web服务器和桌面电脑里都有它的身影。 Java系统在开源库和框架方面已经发展的令人瞠目结舌繁华,在一些付费系统里也火的不得了。 靠着Java这棵大树,市面上已经到处都是由各种企业投资推动的数不清的团队开发工作创造出的成功和成熟的java项目。

如果因为一些小的语言特征而放弃Java这一切基本是不可能的。

我们一起做蛋糕… 也一起吃!

我们所有做的事既要继承Java所有目前的优质资产,同时也要使用函数式语言重新描绘新的编程语言版图。
Scala正好迎合了这种需要,尽管它有很多的竞争对手。 Pizza语言第一个出现的,但它跟今天的Scala比较起来更Scala当初的形式。

我们所知的能在JVM上跑的语言大概有JavaFX, JRuby, Jython, Groovy 等。 大部分都有closures 和其他的一些函数式语言具有的特征,但在Java王国里,这些新生事物并不是那么的血统纯正,它们的特征更像是外来移民,护照很新亮,但有异域口音。

动态语言的流行是无济于事的; “类型”可以通过各种方法隐藏起来,让人感到它的不存在,但是这样很难编译出原生的Java代码了。 这是个很大的问题,特别是你写出的对象需要拿到第三方类库里去处理时。 有时候各种语言之间很难交互,通常需要一个解释器,就像JSR233 Scripting API 或 the Bean Scripting Framework 那样。

Scala却有与生俱来的优势,它和Java的结合是如此的紧密,它能像自己本身的类型那样处理Java类型。 它并不像一个外来移民,而是一个侨胞,而且是有护照的公民。
你从外面看,Java和Scala编译出来的代码是一模一样的,没有区别,这有点让人难以置信,但可以明确的告诉大家,Scala最初就是这样设计出来的。 当你把Scala当作一种函数式语言时,你会更惊奇的发现,它把面向对象和函数式的两种风格以其优雅的方式完全融合统一起来。

正因为它和Java是如此紧密的联系,你可以把Scala当作Java临时的替代品,它绝对不会强制你用任何的函数式风格的代码书写。 它的类型引用,简洁的属性存取,以及带有成员变量参数的构造函数,你几乎可以把它当作一种“风格简洁的Java”。 除了上述的优点外,我们可以称赞Scala为某些方便比Java面向对象更成功的语言:

  • 一切皆为对象,包括数值和函数。
    在Java中,方法不是对象,更别提基本数据类型了。 2.toString在Scala里是一个合法的语句。
  • 它抛弃了静态类成员,Java的这个问题可以追溯到它所效仿的C++上,是个历史错误。 C++本身就是个混合型的语言,它的设计目标就是要兼容过程式的C语言,同时也要支持对象结构。 静态成员不是完全的可面向对象,因为他们不能实现接口,以及向普通成员那样的多形性和覆盖、过载。 当你把一个对象当作参数传入一个函数时,静态成员是不可用的。
    相反,Scala提供了singleton objects, 这样这种问题就不存在了。 Scala里新的companion概念可以让你使用singleton去访问具有相同类名的实例上的一个有约束限定的成员,这样你就可以把静态成员的权限复制出来。
  • 类上所有的属性都实现了behind-the-scenes,就像是个隐藏域,而且有针对它的一对Get和Set隐藏方法。 那些任何人都可以直接修改的内部属性将不再被允许公共访问。

在将来,虚拟类的概念将会在Scala里出现,那样后Scala对对象的支持将会有更惊人的表现。

函数式编程对下面的特征进行了支持:

  • 对递归函数的尾调用(tail-call)优化
  • 模式匹配
  • 第一类函数和高级函数
  • 局部函数(可以接受任何输入值)
  • 局部套用(Currying)和函数局部应用
  • 闭包
  • 简洁的声明常量值的语法定义,很好的支持常量集合的类库
  • continuations (scala 2.8 新增)

[9] Except for the scala library import
[10] No more of those endless get/set methods to litter your code
[11] Actually it is, but you have to do tricky stuff with reflection.
[12] These methods don’t use the same cumbersome naming convention as Java’s bean accessors, although generation of such accessors can be explicitly requested by using the @BeanProperty annotation.

所有的这些都意味着什么?

函数式编程已经证实了它的实力,快速增长的开发者人数是最好的证明。
Scala向大家演示了如何在不牺牲面向对象思维模式下接受函数式设计模式的概念。 它同时也向大家显示了如果将这两种风格的语言如何融合到一起变成一个强壮丰满的新语言,不带任何的形式的勉强。

一旦你了解了基本语法并对闭包、第一类属性、高阶函数、traits,、immutable refs等概念有了认识,它的各种特点的相互结合会向你展示它更深层次的潜质。 语言生命里的一些设计思想的选择和确定最终导致了一个增效作用;
我们认定这种新一代的对象-函数式的设计正是使Scala今天如此成功的关键。

With future articles, I hope to further explain this exciting language by covering some of the ideas that are helping to spread its growing popularity.


[13] I’m proud to rescue this word from its mindless enslavement by corporate jargon. Terminology is important to us developers, we should fight for our political prisoners!

About the Blogger

Kevin Wright has finally settled back in London to work on market analysis for the telecoms industry after having worked his way around Europe in manufacturing, finance and even online gaming. He’s a self-appointed Scala Evangelist and an active participant in every forum he can find, where he’s currently trying to build interest in the London Scala Users’ Group.
分享这篇文章:

11 Responses to 对象-函数式编程简史

  1. 落伍的程序员 says:

    只知道C……

  2. evan says:

    嗯,我也有这个想法,因为我看到Java语言的输出太过于繁琐,而shell的输入输出很简单,所以搞一个自己的编译器,可以支持的操作。当然还想加一些其他的东西,比如javascript里的一些很方便的语法。

    我想,以后编程语言的语法会变成可定制的,程序员喜欢什么样的语法就用什么样的语法。

    • Adam says:

      “我想,以后编程语言的语法会变成可定制的,程序员喜欢什么样的语法就用什么样的语法。”

      不错,这样的话,代码的可维护性就很好了,在团队合作中,因为每个程序员都各自使用不同的语法,所以谁都可以很清楚地知道别人写的代码是什么意思,后来的程序员也可以很容易的读懂先前的程序员所写的代码。

  3. lost_alien says:

    scala枪文。。。兽不鸟了

  4. les says:

    “当你把一个对象当作参数传入一个函数时,静态成员是不可用的。” ??

  5. 伽达默尔 says:

    scala函数式特性和面向对象用起来确实很好。

    但是语法上太过于另类了,把一批人挡在门外了。

  6. clojure says:

    scala的语法糖太多了,如果你不想甜死的话,最好别学了。想学jvm上函数式语言,学clojure吧,语法少,两三句话就能说明白。

  7. 123 says:

    我去看看clojure都不会去碰scala的。

  8. dongguangming 对这篇文章的反应是赞一个

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据