深入理解Scala(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-22 01:14:09

点击下载

作者:[美] Joshua D. Suereth 苏瑞茨

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

深入理解Scala

深入理解Scala试读:

前言

Joshua Suereth 似乎是我所知的最全面的程序员之一,熟悉各种编程语言和技术。他是高性能系统、构建工具、类型理论以及其他很多领域的专家。同时他在教学方面也极有才能。这些特质的结合使《深入理解Scala》成为一本与众不同的书。

这本书深入探讨了Scala里几个较为复杂的领域,包括类型系统的高阶内容、隐式转换、特质的组合技巧、集合、Actor、函数式编程的范畴论等,而且不是干巴巴地讲述语言和库的概念。这本书里充满各种实用的建议和最佳实践来帮助读者学习怎样把Scala 里较少被掌握的部分应用到工作中。书中的解释和例子展现出Joshua用Scala构建大型可伸缩系统的丰富经验。《深入理解Scala》不是一本入门级的书,而是有经验的Scala程序员向专家水平进阶的参考读物。书中所教的都是在构建灵活且类型安全的库时非常好用的技巧。其中很多都是“隐藏在民间”的技巧,第一次在本书中落在纸面上。

我还特别为另一件事而高兴:这本书填补了一个空白——它把正式的Scala语言规范中的关键部分解释给了不是专门研究语言的程序员们。Scala 是少数几个有正式的语言规范的编程语言之一。语言规范主要包含高度程序化的文本和数学公式,所以不是所有人都愿意一读。Joshua的书在解释语言规范里的概念时做到了既权威又可理解。Martin OderskyScala语言的创始人首席程序员RESEARCH GROUP,EPFL

关于本书

《深入理解Scala》是一本使用Scala的实用指导书,深入地探讨了一些必要的主题。这本书撷取了入门书籍忽略的主题,使读者可以写出符合Scala惯用法的代码,理解使用高级语言特性时的取舍。尤其是,这本书详细讲解了Scala的隐式转换和类型系统,然后才讨论怎样使用它们来极大地简化开发。本书提倡“混合风格”的Scala编程,结合使用不同编程范式以达成更大的目标。

谁应当阅读本书

本书适合新或中等水平的Scala开发人员来提升他们的开发技能。这本书在覆盖Scala 中一些非常高阶的概念的同时,也尽量考虑了学习Scala的新手的需求。

本书的读者应当学过Java或别的面向对象语言。具有Scala经验可有助于读者阅读本书,但这种经验并不是必需的。本书覆盖Scala2.7.x到Scala2.9.x版本。

路线图

本书从讨论Scala的“禅”,也就是Scala的设计哲学开始——Scala是把多种概念混合,达成1+1 大于2 的效果。特别讨论了三组对立的概念:静态类型与表达力,函数式编程与面向对象编程,强大的语言特性与极简的Java集成。

第2章讨论Scala的核心规则。这些规则是每个Scala程序员都应当了解并在日常开发中使用的。这一章的内容适用每个Scala程序员,覆盖了那些使Scala如此卓越的基本内容。

第3章是关于编码风格及相关问题的插话。Scala带来了一些新东西,任何Scala编码风格指导都应当反映这些内容。有些来自流行的编程语言如Ruby和Java的常用规则实际上会妨碍你写出好的Scala代码。

第4章覆盖了Scala的mixin继承带来的面向对象设计的新问题。每个Scala程序员都感兴趣的一个主题是早期初始化,这个主题在别的书里很少谈到。

谈完面向对象后,本书接着讨论隐式转换系统。在第5章,我们不是简单地讨论最佳实践,而是深入理解Scala的隐式转换机制。这章对于所有想写出有表达力的库和代码的Scala程序员都是必读章节。

第6章专注于Scala的类型系统。这章讨论Scala里“类型”的各种表现形式,以及如何利用类型系统来强制约束。这章接着进入到对高阶类型的讨论,然后深入到存在类型的讨论作为结束。

第7章讨论Scala语言最高阶的运用模式——类型系统和隐式转换的交叉使用。这种交叉使用带来了很多有趣而强大的抽象,典型的就是类型类模式。

讨论完Scala最高阶的部分后,在第8章,我们讨论Scala的集合库。内容包括Scala 集合库的设计和性能,以及如何利用强大的类型机制。

第9章开始讨论Scala的Actor。Actor是一种并发机制,当正确使用时,可以提供极大的吞吐量和并行性。这一章从讨论基于Actor的设计入手,以展示Akka actors库默认提供的最佳实践作为结束。

第10章内容覆盖Java与Scala的集成。虽然Scala与Java的兼容性好于JVM上的大部分其他语言,但两者还是存在一些特性上的不匹配。这些不匹配的角落里容易发生Scala-Java集成问题,这章提供了几个简单规则来帮助避免这些问题。

第11章拿来了范畴论里的概念并使之实用化。在纯函数式编程里,很多来自范畴论的概念已经被应用到代码里。这些概念有点类似面向对象的设计模式,但是要抽象得多。虽然这些概念的名字挺吓人——这在数学上很常见——但它们其实极有实用价值。不讨论这些抽象概念就没法完整讲述函数式编程,本书尽力使这些概念更现实(不那么抽象)。

代码下载和约定

为了使代码显示区别于正文,本书中的所有代码都用等宽字体显示。很多代码清单里有一些用来指出关键点的注解。我已经尽量通过增加换行和使用缩进来调整代码格式,使注解能够在页面空间里显示完整,但偶尔还会有一些很长的语句不得不用换行连接符号。

书中所有例子的源代码可以在 www.manning.com/ScalainDepth 和 https://github.com /jsuereth/scala-in-depth-source获取。要运行代码示例,读者需要安装Scala以及(可选的) SBT构建工具。

全书包含很多代码示例,较长的代码有明显的清单标题,较短的代码直接显示在文本行之间。

作者在线

购买本书的同时,您获得了免费访问 Manning 出版社运营的私有网络论坛的权利,您可以在那里对本书做评论、询问技术问题、向作者和其他用户寻求帮助。要访问和订阅论坛,请在Web浏览器里打开地址www.manning.com/ScalainDepth。这个页面提供了在注册后如何使用论坛,有哪些可用的帮助,以及论坛的指导规则等信息。

Manning对读者的承诺仅是提供一个能够让读者之间、读者和作者之间进行有意义的对话的场所,并不承诺作者在论坛上的投入度。作者在论坛上的投入是义务的(无偿的)。我们建议您向作者提出一些有挑战的问题,以免他失去兴趣。

只要书还在印刷,作者在线论坛以及发生过的讨论将保持可访问。

作者简介

Josh Suereth 是Typesafe 公司(Scala 背后的公司)的一名高级软件工程师。从2007年了解到Scala这门美丽的语言后,他就成了Scala的狂热分子。他在2004年开始了软件开发者的职业生涯,一开始先在C++、STL和Boost上磨砺技能。当时Java正在狂热传播,他的兴趣迁移到Web部署的基于Java的解决方案来帮助健康部门发现疾病的爆发。

他在2007年将Scala引入公司的代码库,然后迅速染上了Scala狂热症,他对Scala IDE、maven-scala-plugin和Scala本身都做出了贡献。今日,Josh已经是好几个开源Scala 项目的作者,包括Scala自动化资源管理库和PGPsbt插件,他还是Scala生态系统的一些关键组件的贡献者,比如说 maven-scala- plugin。他现在就职于 Typesafe 公司,做包括从构建MSI到侦测性能问题等各种事情。

Josh定期地通过文章和演讲分享他的专业知识。他喜欢在海滩边散步还有喝黑啤。

致谢

在这本书出版的过程中得到了很多人的帮助。我将尽量全部列出,但我确信帮助我的人太多,实非我的小小脑袋能够全部记住。这本书让我知道我有非常多高水准的朋友、同事,还有家庭。

最应当感谢的是我的妻子和孩子,他们不得不忍受一个一直躲在角落里写书,该搭手帮忙的时候也不出来的丈夫和父亲。没有任何作者能够在没有家庭支持的情况下写一本书,我也不例外。

接着我要感谢Manning出版社和工作人员为使我成为一个真正的作者所做的事。他们不仅做了审阅、排版的工作,还帮助我提高有助于清晰沟通的写作技巧。对整个出版团队,我感激不尽,尤其要感谢Katherine Osborne 容忍我的不断拖稿,宾夕法尼亚州的荷兰语式的语句和经常的拼写错误。Katherine非常注意搜集对本书的读者之声,那些读过MEAP(早期发行版)的读者应该能注意到本书的进步。

下一个应该感谢的群体是帮助我提高技术材料和文字写作的 Scala 专家和非专家们。在我写本书的差不多同时期,Tim Perret 正在为Manning 出版社写《Lift in Action》。和Tim的讨论既有益又激励。对我不幸的是他先写完了他那本书。Justin Wick 是本书很多内容的审阅者和协作者,并最终帮助我使这本书所针对的读者范围比我开始时所想的大得多。他同时也是付印前最后一个审阅手稿和代码的人。Adriaan Moor,一如既往地在讨论类型系统和隐式解析时指出我所有的错误,使讨论既实用又正确。Eric Weinberg 是我的老同事,帮助提供了本书中给非Scala 程序员的指导。Viktor Klang 审阅了“Actors”章节(和整本书)并提供了改进建议。也要感谢Martin Odersky 的支持,感谢他给本书写序。感谢Josh Cough,他是一个能在需要的时候跟我激荡创意的家伙,还有 Peter Simany,感谢他用电子邮件发给我对于整本书的非常详尽、细致、完整、极好的评审意见。

Manning 还联系了以下这些评审者,他们在不同阶段阅读了本书的手稿,我想在这里感谢他们无价的洞见和评论:JohnC.Tyler、Orhan Alkan、Michael Nash、John Griffin、Jeroen Benckhuijsen、David Biesack、Lutz Hankewitz、Oleksandr Alesinskyy、Cheryl Jerozal、Edmon Begoli、Ramnivas Laddad、Marco Ughetti、Marcus Kazmierczak、Ted Neward、Eric Weinberg、Dave Pawson、Patrick Steger 、Paul Stusiak、Mark Thomas、David Dossot、Tariq Ahmed 、KenMc Donald、Mark Needham和James Hatheway。

最后,我要感谢所有MEAP版的评审者,他们给予我非常有价值的反馈,感谢他们的支持和本书付印前所得到的审阅意见。他们不得不忍受大量的拼写错误,使这本书由粗糙的初稿转变为最终的版本。

自序

2010 年秋,Manning 出版社的Michael Stephens 联系我写一本关于Scala 的书。当时我就职于一家主营虚拟化/安全方面的小创业公司,期间我学习并在工作代码中应用了Scala语言。在Michael和我的第一次会谈中我们讨论了Scala的生态系统和什么样的书对社区最有价值。

我认为Scala需要一本“实用Scala”这样的书来指导那些Scala新人。Scala是一种美丽的语言,但它一下子带给程序员很多新概念。我曾看着Scala社区慢慢地识别出最佳实践,慢慢地发展出完全属于“Scala”的编码风格,但我一直不确定我是不是写这本书的合适人选。当各种条件逐渐齐备——我对这个主题充满激情,有足够的自由时间来做研究,有社区牛人的支持——于是我决定写这本书。

在写作过程中我学到了很多,写这本书耗时如此之久的原因之一在于 Scala 的不断进化和不断涌现的新最佳实践。另一个原因则是我意识到自己的知识在 Scala 的某些领域非常不足。我想在这里告诉所有有志写书的作者,写书的过程能够让你成为专家。在开始写书前你可能觉得自己本来就是专家,但我要告诉你真正的专业技能是在教导他人,是在尽力把复杂的概念清晰地解释给你的读者的过程中伴随着血、汗和泪成长起来的。

如果没有一直支持我的妻子、伟大的出版社、了不起的Scala程序员社区和愿意阅读我不同阶段的手稿,指出拼写错误,给出改进建议的读者,我绝不可能完成写这本书的旅程。感谢你们让这本书远超我独自一人能够达到的水平。

第1章 Scala——一种混合式编程语言

本章包括的内容:

简要介绍Scala语言

剖析Scala语言的设计思想

Scala 是一种将其他编程语言中的多种技巧融合为一的语言。Scala 尝试跨越多种不同类型的语言,给开发者提供面向对象编程、函数式编程、富有表达力的语法、静态强类型和丰富的泛型等特性,而且全部架设于 Java 虚拟机之上。因此开发者使用Scala时可以继续使用原本熟悉的某种编程特性,但要发挥Scala的强大能力则需要结合使用这些有时候相互抵触的概念和特性,建立一种平衡的和谐。Scala对开发者的真正解放之处在于让开发者可以随意使用最适合手头上的问题的编程范式。如果当前的任务更适合用命令式的设计实现,没什么规定禁止你写命令式的代码,如果函数式编程和不可变性(immutability)更符合需要,那程序员也可以尽管用。更重要的是,面对有多种不同需求的问题领域(problem domain),你可以在一个解决方案的不同部分采用不同的编程方法。1.1 Scala的设计哲学

为了理解Scala的哲学,我们需要理解产生Scala的环境:Java生态圈Java(TM)语言在1995年左右进入计算机科学领域,产生了巨大的影响。Java和运行Java的虚拟机开始慢慢地变革了我们的编程方法。在那时候,C++正如日中天,开发者正在从纯C风格的编程转而开始学习如何有效地使用面向对象编程方法。尽管C++有很多的优点,但它也有一些痛点,比如难以分发库(distributing libraries)以及其面向对象实现的复杂度。

Java语言通过提供限制了部分能力的面向对象特性和使用Java虚拟机,同时解决了这两个痛点。Java虚拟机(JVM)允许代码在一个平台上编写和编译后,几乎不费多大劲就能分发到其他的平台上。尽管跨平台问题并没有就此消失,但是跨平台编程的成本极大地降低了。

随着时间的推移,JVM的执行效率越来越高,同时Java社区不断成长。HotSpot (TM)优化技术被发明出来,这样就可以先探测运行环境再进行针对性的代码优化。虽然这使得 JVM 启动速度变慢,但是之后的运行时性能则变得很适合于运行服务器之类的应用。尽管最初并非为企业服务器领域设计的,JVM 开始在此领域大行其道。于是人们开始尝试简化企业服务器应用开发,Enterprise Java Beans(TM)和较新的 Spring Application Framework(TM)出现,帮助程序员更好地利用 JVM 的能力。Java 社区发生了爆炸式的成长,创造出成百万的易于使用的库。“只要你能想到的,基本都能找到Java库”成为一个职场口号。Java语言持续地缓慢进化,努力维持住其社区。

与此同时,部分开发者开始延展他们的羽翼,触及了Java本身设计上的局限之处。Java 简化了一些(编程元素),而社区中的部分成员需要增加一些复杂的,但是可控的元素。第二波创造JVM语言的浪潮掀起并持续至今。Groovy、JRuby、Clojure和Scala 等语言开始将Java程序员带入一个新时代。我们不再局限于一种语言,而是可以有多种选择,每一种语言都有不同的优点和弱点。Scala是其中较为流行的一种。

Scala语言创造者Martin Ordersky是javac 编译器的作者,也是他将泛型引入Java 语言中。Scala语言衍生自Funnel语言。Funnel语言尝试将函数式编程和Petri网结合起来,而Scala的预期目标则是将面向对象、函数式编程和强大的类型系统结合起来,同时仍然要能写出优雅、简洁的代码。将以上多种概念混合的目的是创造出一种既能让程序员真正用起来,同时又能用来研究新的编程范式的语言。事实上它取得了巨大的成功——它作为一种可行的有竞争力的语言已经开始被产业界采用。

要想掌握Scala,你需要理解多种混合在一起的概念。Scala试图将以下三组对立的思想融合到一种语言中。

函数式编程和面向对象编程。

富有表达力的语法和静态类型。

高级的语言特性同时保持与Java 的高度集成。

我们来看一下这些特性是如何融合在一起的。先从函数式编程和面向对象编程概念开始。1.2 当函数式编程遇见面向对象

函数式编程和面向对象编程是软件开发的两种不同途径。函数式编程并非什么新概念,在现代开发者的开发工具箱里也绝非是什么天外来客。我们将通过Java生态圈里的例子来展示这一点,主要来看Spring Application framework 和Google Collections 库。这两个库都在 Java 的面向对象基础上融合了函数式的概念,而如果我们把它们翻译成Scala,则会优雅得多。在深入之前,我们需要先理解面向对象编程和函数式编程这两个术语的含义。

面向对象编程是一种自顶向下的程序设计方法。用面向对象方法构造软件时,我们将代码以名词(对象)做切割,每个对象有某种形式的标识符(self/this)、行为(方法)、和状态(成员变量)。识别出名词并且定义出它们的行为后,再定义出名词之间的交互。实现交互时存在一个问题,就是这些交互必须放在其中一个对象中(而不能独立存在)。现代面向对象设计倾向于定义出“服务类”,将操作多个领域对象的方法集合放在里面。这些服务类,虽然也是对象,但通常不具有独立状态,也没有与它们所操作的对象无关的独立行为。

函数式编程方法通过组合和应用函数来构造软件。函数式编程倾向于将软件分解为其需要执行的行为或操作,而且通常采用自底向上的方法。函数式编程中的函数概念具有一定的数学上的含义,纯粹是对输入进行操作,产生结果。所有变量都被认为是不可变的。函数式编程中对不变性的强调有助于编写并发程序。函数式编程试图将副作用推迟到尽可能晚。从某种意义上说,消除副作用使得对程序进行推理(reasoning)变得较为容易。函数式编程还提供了非常强大的对事物进行抽象和组合的能力。

表1.1 面向对象和函数式编程的一般特点

函数式编程和面向对象编程从不同的视角看待软件。这种视角上的差异使得它们非常互补。面向对象可以处理名词而函数式编程能够处理动词。其实近年来很多Java 程序员已经开始转向这一策略(分离名词和动词)。EJB 规范将软件切分为用来容纳行为的 Session bean 和用来为系统中的名词建模的 Entity bean。无状态Session bean 看上去就更像是函数式代码的集合了(尽管欠缺了很多函数式代码有用的特性)。

这种朝函数式风格方向的推动远不止 EJB 规范。Spring 框架的模板类(Template classes)就是一种非常函数式的风格,而Google Collections 库在设计上就非常的函数式。我们先来看一下这些通用的Java库,然后看看Scala的函数式和混合面向对象编程能怎样增强这些API。1.2.1 重新发现函数式概念

很多现代API设计时都融入了函数式编程的好东西而又不称自己是函数式编程。对于Java 来说,像Google Collections 和Spring 应用框架以Java 库的形式使Java 程序员也能接触到流行的函数式编程概念。Scala更进一步,将函数式编程直接融合到了语言里。我们来将流行的 Spring 框架中的 Jdbc Template 类简单地翻译成 Scala,看看它在Scala 下会是什么样子。

清单1.1 Spring的JdbcTemplate类上的查询方法

现在,来直译一下,我们把接口转换为有相同方法的特质(trait)。

清单1.2 查询方法的Scala直译

简单的直译也很有意思,不过它还是非常的Java。我们现在来深挖一下,特别看看PreparedStatementCreator和RowMapper接口。

清单1.3 PreparedStatementCreator 接口

PreparedStatementCreator 接口只有一个方法。这个方法接受JDBC连接,返回PreparedStatement. RowMapper 接口看上去也差不多。

清单1.4 RowMapper接口

Scala 提供了一等函数(first-class function),利用这个特性,我们可以把JdbcTemplate 查询方法改成接受函数而不是接口作为参数。这些函数应该跟接口里的基础方法有相同的签名。本例中,PreparedStatementCreator 参数可以替换为一个函数,这个函数接受Connection,返回PreparedStatement. RowMapper 可以替换成一个接受ResultSet 和整数,返回某种对象类型的函数。更新后的Scala版本JdbcTemplate如下。

清单1.5 Spring的JdbcTemplate类的Scala版本初版

现在query方法变得更函数式了。它使用了称为租借模式(loaner pattern)的技巧。这种技巧的大意在于让一些主控的实体(controlling entity)——本例中是Jdbc Template——由它来构造资源,然后将资源的使用委托给另一个函数。本例中有两个函数和三种资源。同时,其名字JdbcTemplate隐含的意思是它是个模板方法,其部分行为是有待用户去实现的。在纯面向对象编程中,这一点通常通过继承来做到。在较为函数式的方法中,这些行为碎片(behavioral pieces)成为了传给主控函数的参数。这样就能通过混合/匹配参数提供更多的灵活性,而无需不断地使用子类继承。

你可能会奇怪为什么我们用AnyRef作为第二个参数的返回值。Scala中的AnyRef 就相当于Java里的java.lang.Object。既然Scala支持泛型,即使要编译成jvm1.4字节码,我们也应该进一步修改接口移除AnyRef,允许用户返回特定类型。

清单1.6 Spring的JdbcTemplate类的类型化后的版本

仅稍做转换,我们就创建了一个直接使用函数参数的接口。这比之前略为函数式一点,仅仅是因为Scala的函数特质允许组合。

当你读完本书的时候,你将能做出与此接口完全不同的设计。不过我们现在还是继续查看Java 生态圈里的函数式设计。尤其是Google Collections API。1.2.2 Google Collections 中的函数式概念

Google Collections API 给标准Java 集合库增加了很多功能,主要包括一组高效的不可变数据结构和一些操作集合的函数式方法,主要是Function接口和Predicate(谓词)接口。这些接口主要用在通过Iterables和Iterators类上。我们来看下Predicate接口的使用方法。

清单1.7 谷歌集合库的Predicate接口

Predicate接口非常简单。除了equals方法,它就只有一个“apply”方法。apply方法接受参数,返回true或false。Iterators/Iterables的“filter”方法用到了这个接口。filter 方法接受一个集合和一个谓词作为参数,返回一个新集合,仅包含被predicate的apply 方法判定为true的元素。在find方法里也用到了Predicate接口。find方法在集合中查找并返回第一个满足predicate的元素。下面列出filter和find 方法签名。

清单1.8 迭代器的filter 和find方法

另外还有个Predicates类,里面有一些用于组合断言(与和或等)的静态方法,还有一些常用的标准谓词,如“not null”等。这个简单的接口让我们可以用很简洁的代码通过组合的方式实现强大的功能。同时,因为 predicate 本身被传入到 filter 函数里面(而不是把集合传入到predicate里),filter函数可以自行决定执行断言的最佳方法或时机。比如(filter 背后的集合)数据结构有可能决定采用延迟计算(lazily evaluating)断言的策略,那它可以返回原集合的一个视图(view)。它也可能决定在创建新集合的时候采用某种并行策略。关键是这些都被抽象掉了,使得库可以随时改进而不影响用户的代码。

Predicate接口自身也很有趣,因为它看上去就像个简单的函数。这个函数接受某个类型T,返回一个布尔值,在Scala 里用T => Boolean 表示。我们用Scala 来重写一下filter/find方法,看看它们的函数签名怎样定义。

清单1.9 迭代器的filter 和find方法的Scala 版本

你会立刻注意到Scala 里无需显示的标注“?superT”,因为Scala 的Function 接口已经恰当地标注了协变(Covariance)和逆变(Contravariance)。如果某类型可以强制转换为子孙类,我们称为协变(+T 或? extends T),如果某类型可以强制转换为祖先类,我们称为逆变(-T 或? super T)。如果某类型完全不能被强制转换,就称为不变(Invariance)。在这个例子里,断言的参数可以在需要的时候强制转换为其祖先类型。举例来说,如果猫是哺乳动物的子类,那么一个针对哺乳动物的断言也能用于猫的集合。在Scala中,你可以在类定义的时候指定其为协变/逆变/不变。

那么在Scala里怎么组合断言呢?我们可以利用函数式组合的特性非常方便地实现一些组合功能。我们来用Scala实现一个新的Predicates模块,这个模块接受(多个)函数断言作为参数,提供它们的常用组合函数。这些组合函数的输入类型应该是T =>Boolean,输入类型也是T=> Boolean。初始的(组合前的)断言应该也是T=>Boolean 类型。

清单1.10 Predicates的Scala版

现在我们开始踏入函数式编程的领域了。我们定义了一等函数(first-class function),然后把它们组合起来提供新的功能。你应该注意到了or方法接受两个断言,f1和f2,然后产生一个匿名函数,这个函数接受参数t,然后把f1(t)和f2(t)的结果“or”一下。函数式编程也更充分地利用了泛型和类型系统的能力。Scala 投入了很多心血来减少使用泛型时的困难,使泛型可以被“日常使用”。

函数式编程并不仅仅就是把函数组合起来而已。函数式编程的精髓在于尽可能地推迟副作用。上例中的Predicate对象定义了一个简单的组合机制,只是用来组合谓词(而不执行)。直到实际的谓词传递给 Iterables 对象后才产生副作用。这个区分很重要。我们可以用Preicate对象提供的辅助方法把简单的谓词组合成很复杂的谓词。

函数式编程给我们提供了手段来推迟程序中改变状态的部分。它提供了机制让我们构造“动词”,同时又推迟副作用。这些动词可以用更方便推理(reasoning)的方式组合起来.直到最后,这些“动词”才被应用到系统中的“名词”上。传统的函数式编程风格是要求把副作用推到越晚越好。混合式面向对象-函数式编程(OO-FP),则是一种混合式风格(the idioms merge)

接下来我们看看Scala怎么解决类型系统和富有表达力的代码之间的矛盾。1.3 静态类型和表达力

开发人员中有一个误解,认为静态类型必然导致冗长的代码。之所以如此是因为很多继承自C的语言强制要求程序员必须在代码中多处明确地指定类型。随着软件开发技术和编译器理论的发展,情况已经改变。Scala 利用了其中一些技术进步来减少样板(boilerplate)代码,保持代码简洁。

Scala做了以下几个简单的设计决策,以提高代码表达力。

把类型标注(type annotation)换到变量右边。

类型推断。

可扩展的语法。

用户自定义的隐式转换。

我们先看看Scala是怎么改变类型标注的位置的。1.3.1 换边

Scala把类型标注放在变量的右侧。像Java或C++等几种静态类型语言,一般都必须声明变量、返回值和参数的类型。在指定变量或参数的类型时,延续自C的做法是把类型标注放在变量名的左边。对于方法的参数和返回值来说,这是可以接受的,但在构造不同风格(style)的变量时就容易产生混淆。C++是最好的例子,它有很丰富的变量修饰符选项,比如volatile、 const、指针和引用等。

清单1.11 C++里的整型变量示例

把变量的类型和其修饰符混在一起的做法引致一些极其复杂的类型定义。而Scala,和其他几种语言,则把类型标注放在变量的右边。把变量类型和修饰符分开能帮助程序员在读代码时减少一些复杂性。

清单1.12 Scala里的整型变量示例

上例演示了仅仅把类型标准搬到变量右边,我们就可以使代码简化不少,而这还不是全部,Scala能通过类型推断进一步减少语法噪音。1.3.2 类型推断

只要能够进行类型推断, Scala 就会执行类型推断,从而进一步降低语法噪音(syntactic noise)。类型推断是指编译器自行判断类型标注,而不是强迫用户去指定。用户当然可以提供类型标注(如果他想),但他也可以选择让编译器来干这活。

清单1.13 Scala里的类型推导

这个特性能够极大地减少在其他强类型的语言中常见的语法噪音。Scala更进一步对传递给方法的参数也进行某种程度的类型推断,特别是对(作为参数的)一等函数。如果已知一个方法接受一个函数参数,编译器能够推断出函数字面量(function literal)里面使用的类型。

清单1.14 函数字面量类型推导1.3.3 抛开语法

Scala 语法采取了一种策略:如果一行代码的含义非常明确,就可以抛弃掉一些冗长的语法。这个特性可能会让刚学习Scala的用户感到困惑,但如果使用得当,这个特性是极其强大的。我们来看个重构代码的例子,从一个完全符合Scala标准语法的代码,简化为Scala社区的惯用写法。下面是Scala实现的Quicksort函数。

清单1.15 冗长版的Scala快速排序

这段代码接受一个 T 类型的列表,T 可以被隐式转换为 Ordered[T]类型(T <%Ordered[T])。我们会在第6章详细讨论类型参数及其约束,目前先不要关注于此。简单来说,我们需要一个列表,里面的元素是可以排序的,所以该元素应该有个判断是否小于的方法“<”。然后我们检查列表,如果是空列表或 Nil,我们就返回个 Nil 列表。如果列表里有内容,我们取出列表的头(x)和尾(xs)。我们用头元素来把尾列表切分成两个列表。接着我们对这两个列表分别递归调用quicksort方法。在同一行上,我们把排序后的列表和头元素结合为一个完整的(排序后的)列表。

你可能会想,“哇哦,Scala 代码看上去真丑”。就这个例子来说,你可能是对的。代码相当凌乱,难以阅读。有很多语法噪音掩盖了代码本身的含义。不仅如此,qsort 后面还有很多吓人的类型信息。让我们拿出手术刀,割掉那些讨厌的玩意。首先,Scala 可以自行推断分号。编译器会假定一行结束就是一个表达式的结束,除非你在行末留了什么未完结的语法片段,比如方法调用前的那个“.”(like the.beforea method call)。

光删除分号显然没多大帮助。我们还需要应用“操作符”(operator notation)。操作符是Scala 的一个能力,可以把方法当作操作符。无参数的方法可以用作后缀操作符(postfix operator),只有一个参数的方法可以当作中缀操作符(infix operator)。还有一些对特殊字符的专门规定,比如方法名的最后一个字符如果是“:”,则方法的调用方向反转。下面的代码演示了这些规则。

清单1.16 操作符

在定义匿名函数时(又称 lambda),Scala 提供了占位符语法。可以使用“_”关键字作为函数参数的占位符。如果使用多个占位符,每个相应位置的占位符对应于相应位置的参数。这种写法通常在定义很简单的函数时使用,比如我们的 Quicksort 例子里面比较元素大小的那个函数字面量。

我们可以结合使用占位符语法和操作符来改进我们的快速排序算法。

清单1.17 较简洁版的Scala快速排序

Scala 不仅为简单场景提供了语法糖,它还提供了隐式转换和隐式参数机制让我们可以扭曲(bend)类型系统。1.3.4 隐式转换概念早已有之

Scala 隐式转换是老概念的新用法。我是在 C++的基础类型上第一次接触到隐式转换的概念。只要不损失精度,C++可以自动转换基础类型,比如我可以在声明long值的时候给它个 int 。对于编译器来说,实际的类型“double”,“float”, “int”和“long”型都是不同的,但在混用这些值时编译器尝试智能地去“做正确的事”(Do the Right Thing (TM))。Scala 提供了相同的机制,但是是作为一个语言特性给大家使用(而不是只让编译器用)。

Scala会自动地加载scala.Predef对象,使它的成员方法对所有程序可用。这样可以很方便地给用户提供一些常用函数,比如可以直接写 println 而不是 Console.println 或System.out.println。Predef 还提供了“基础类型拓宽”(primitive widenings)。也就是一些能够把低精度类型自动转换为高精度类型的隐式转换。我们来看一下为 Byte 类型定义的转换方法。

清单1.18 scala.Predef对象里的字节转换方法

这些方法只是简单地调用运行时转换(runtime-conversion)方法。方法名前面的 implicit关键字说明编译器会在需要对应的类型时尝试对Byte调用对应的方法。比如说,如果我们给一个需要 Short 类型的方法传了一个 Byte,编译器会调用隐式转换方法byte2short。Scala还把这个机制更进一步:如果对一个类型调用一个它没有的方法,Scala 也会通过隐式转换来查找这个方法。这比仅仅提供基础类型转换提供了更多的便利。

Scala也把隐式转换机制作为扩展Java基础类型(Integer、 String、Double等)的一种手段。这允许Scala直接使用Java类以方便集成,同时提供更丰富的方法(method)以便利用Scala更为先进的特性。隐式转换是非常强大的特性,也因此引起一些人的疑虑,关键在于知道如何以及何时使用隐式转换。1.3.5 使用 Scala 的 implicit 关键字

用好隐式转换是操纵Scala类型系统的关键。隐式转换的基础应用场景是按需自动地把一种类型转换为另一种,但它也可以用于有限形式的编译时元编程(limited forms of compiler time metaprogramming)。要使用隐式转换必须把它关联到某个作用域。可以通过伴生对象或明确的导入来做关联。

implicit关键字在Scala里有两种不同用法。第一种用法是给方法声明一种特殊参数,如果编译器在作用域里找到了合适的值就会自动传递给方法。这可以用来把某API的某些特性限定在某个作用域里。因为implict 采用了继承线性化(inheritance linearizion)的查找策略,所以可以用来修改方法的返回值。这使用户可以写出非常高级的API以及玩一些类型系统的小把戏,在Scala collections API 里就使用了这种技术。这些技术会在第7章详加解释。

implicit 关键字的另一种用法是把一种类型转换为另一种。有两种场景会发生隐式转换,第一种场景是当你给一个函数传递参数的时候,如果Scala发现函数需要的参数类型(跟传给它的)不一样,Scala 会首先检查类型继承关系,如果没找到,就会去查找有没有合适的隐式转换方法。隐式转换方法只是普通的方法,用 implicit 关键字做了标注,该方法接受一个参数,返回某些结果(译著:实际上是接受转换前的参数,返回转换后的结果,然后Scala用转换后的结果作为参数去调之前那个函数)。第二种场景是当调用某类型的某方法时,如果编译器发现该类型没有这个方法,Scala 会对该查找适用于该类型的隐式转换,直到找到一个转换后具有该方法的结果,或者找不到(编译出错)。这种做法在Scala 的“pimp my library”模式中得以应用,这些内容也会在第7 章详解。

这些特性的组合给Scala带来了非常有表达力的语法,同时保持其高级的类型系统。创造有表达力的库需要深入理解类型系统,也必须彻底理解隐式转换的知识。第6章会全面地覆盖类型系统的知识。Scala类型系统跟Java也能很好地交互,这是Scala的关键设计之一。1.4 与JVM的无缝集成

Scala的吸引力之一在于它与Java和JVM的无缝集成。Scala与Java有很强的兼容性,比如说Java类可以直接映射为Scala类。这种紧密联系使Java到Scala的迁移相当简单,但在使用Scala 的一些高级特性时还是需要小心的,Scala 有些高级特性是Java 里没有的。在Scala语言设计时已经小心地考虑了与Java无缝交互的问题,用Java写的库,大部分可以直接照搬(as-is)到Scala里。1.4.1 Scala 调用 Java

从Scala里调用Java库是透明的,因为Java惯用法直接对应到Scala惯用法。Java 类变成Scala类,Java接口变成Scala抽象特质(trait),Java静态成员被加入Scala伪对象(pseudo Scala object)。以上结合Scala 的包导入机制和方法访问机制,使Java 库感觉就像原生Scala库一样。虽然有过度简化之嫌,但一般情况下是直接就能用。举例来说,我们有个Java类,它有构造器,有一个成员方法和一个静态辅助方法。

清单1.19 简单Java对象

现在我们在Scala里用这个Java类。

清单1.20 在Scala里使用简单Java对象

这种映射非常自然,使用Java类库成为用Scala做开发时很自然的事。除了有这种紧密集成,你通常还能找到Java 库的瘦Scala 包装(thin Scala wrapper),提供一些Java API无法提供的高级特性。尝试在Java中使用Scala库时,这些特性就变得很凸显。1.4.2 Java 调用 Scala

Scala尝试以最简单的方式把其特性映射到Java。大部分Scala特性可以一对一地简单映射为Java特性,比如类、抽象类、方法等。Scala有些相当高级的特性就难以简单的映射了,包括对象、一等函数和隐式转换等。

Scala对象映射到Java

虽然 Java 的静态(statics)映射为 Scala 对象,但 Scala 对象实际上是个单例类(singleton)的实例,在编译时此单例类命名为对象名后加个$符号。这个单例类里有个Module$静态成员,指向其唯一实例。Scala还提供了转发静态方法的能力,这些静态方法位于伴生类里(一个与object同名的类)。虽然Scala本身并没有使用静态方法,但是它们给从Java里调用Scala提供了便利的语法。

清单1.21 简单Scala对象

清单1.22 在Java里使用简单Scala对象

Scala函数映射到Java

Scala 鼓励使用作为对象的函数(function as object),或称一等函数。到Java1.6 为止,Java语言(和JVM虚拟机)都还没有这样的概念。因此Scala创造了函数特质符号(notion of Function traits),一共有23 个特质代表0 到22 个参数的函数。当编译器碰到需要把方法当做函数传递的场景时,就构造一个(参数数量)合适的特质的匿名子类。由于特质无法映射到Java,从Java中传递一等函数到Scala也就很难实现,但也不是完全没办法。

清单1.23 在Java里调用需要函数作为参数的Scala方法

我们在Scala里构造了一个抽象类,这样Java实现起来就比function特质要容易。虽然这稍微简化了Java端的实现,但还是没百分百地简化问题。Java类型系统和Scala对类型的编码中间还是存在不匹配,我们还是需要在调用Scala时对函数类型做强制转换。

清单1.24 在Java里实现一等函数

所以在组合使用Scala和Java时,使用一等函数和更函数式的编程方法是可能的。但是还存在其他的手段来达到这个目的。这方面更详细的讨论以及其他Java/Scala交互相关的问题请见第10章。如你所见,Scala可以很好地集成现有的Java程序,也可以和Java代码一起使用。Java/Scala交互并非在JVM上跑 Scala的唯一好处,JVM本身也带来了巨大的好处。1.4.3 JVM 的优越性

前文曾经提过,Java的很多好处是JVM提供的。通过字节码,可以几乎原封不动地把库分发到很多不同的平台。JVM在很多平台上经过仔细的测试,而且经过了大规模的企业部署。不仅测试完善,还在Java平台的性能方面投入了极大关注。HotSpot编译器能在运行时对代码进行各种优化。用户可以简单地升级JVM,然后立刻体验到性能提升,而无需打补丁或重编译。

HOTSPOT-ING

在JVM上运行Scala的首要好处是HotSpot运行时优化器。它会对程序进行运行时分析,自动对JVM字节码进行调优。Scala运行于JVM上,自然就免费得到了这些优化。JVM每次发布都提升了HotSpot编译器,也就连带着提升了Scala的性能。HotSpot 编译器使用了多种技术,包括以下这些。

方法内联(Method inlining)。

栈替换(On Stack Replacement)。

逃逸分析(Escape Analysis)。

动态去优化(Dynamic De-optimization)。

方法内联是指 HotSpot 能够判断是否能在调用点直接把被调的小方法的内容嵌入进去。这是C++里我很喜欢的一项技术,而HotSpot能够动态判断这样做是否对性能有优化。栈替换指HotSpot能够判断一个变量应该放在栈(Stack)里还是堆(Heap)里。我记得用C++的时候一个大问题就是在声明变量的时候应该把它放在栈里还是堆里。现在 HotSpot 可以为我回答这个问题。逃逸分析是指HotSpot分析判断各种东西是否逸出(escape)了特定作用域。这项技术主要用来在同步方法调用限定于某个作用域时减少锁开销,但也可以用于其他情况。动态去优化是HotSpot的一个关键特性,它有能力判断一个优化是否事实上没有提升性能,然后取消该优化,改用其他优化。以上特性的组合构成了很有吸引力的图景,这就是为什么各种新/旧语言(比如Ruby)都很渴望在JVM上运行。1.5 总结

本章中,你学到了一些 Scala 的设计理念。设计 Scala 的初衷在于把不同语言中的多种概念融合起来。Scala融合了函数式和面向对象编程,尽管显然Java也已经这么做了。Scala 精选其语法,极大地减少了语言中的繁冗之处,使一些强大的特性可以优雅地表达,比如类型推断。最后,Scala和Java能够紧密集成,而且运行在Java虚拟机上,这或许是让 Scala 变成一种实用选择的最重要的一点。几乎不花代价就可以把 Scala 用于我们的日常工作中。

因为Scala融合了多种概念,Scala的用户发现他们要在函数式编程、面向对象、与现有 Java 应用集成、富有表达力的库 API 和通过类型系统确保需求( enforcing requirements through the type system)等方面做微妙的平衡,根据手头的需求做出的决定往往是最佳的。正是这些对立概念的交织,使得Scala成长兴盛,而这也正是需要最多关注的地方。本书会帮你趟过艰难领域,让你看到Scala闪耀的地方。

我们先来了解一些每个Scala程序员在做Scala编程时都需要知道的关键概念。

第2章 核心规则

本章包括的内容:

使用Scala交互模式(Read Eval Print Loop 简称REPL)

面向表达式编程

不变性(Immutability)

Option类

本章内容覆盖了每个新Scala开发者都需要知道的几个主题。本章不会深入到每个主题里,但是会讲到可以让你自己去接着探索的程度。你将学会使用 REPL,学会如何利用这个工具做软件的快速原型开发。然后我们会学到面向表达式编程,并从另一个视角来看控制结构是怎么回事。在此基础上,我们来研究不变性,研究不变性为什么能帮助我们极大地简化程序,并且能帮助程序在并发环境下更好地运行。2.1 学习使用Scala交互模式(REPL)

Scala 提供了很多学习材料帮助你学习核心语言内容,有很多在线的教程、示例和项目可以去研究。但是Scala提供的最重要的一个工具是交互模式(REPL)。REPL是一个交互式解释器,可以即时编译、运行代码并返回结果。假定你已经在机器上装好了Scala,也设置了正确的路径,那么在命令行下运行scala 命令就可以启动Scala REPL。启动Scala REPL 后屏幕上会输出如下内容:

后面的代码示例中,我会用scala>提示这是输入到REPL的内容。接下来的一行是REPL的输出。我们在REPL里快速做几个例子,看看会得到什么输出。

你应该注意到了在我们输入解释器的每个语句后,它会输出一行信息,类似 res0:java.lang.String = Hello。输出的第一部分是REPL 给表达式起的变量名。在这几个例子里,REPL为每个表达式定义了一个新变量(res0到res3)。输出的第二部分(:后面的部分)是表达式的静态类型。第一个例子的类型是 java.lang.String,最后一个例子的类型则是 scala.util.matching.Regex。输出的最后一部分是表达式求值后的结果的字符串化显示。一般是对结果调用 toString 方法得到的输出,JVM 给所有的类都定义了toString 方法。

图2.1 REPL的返回值

如你所见,REPL 是一种测试 Scala 语言及其类型系统的强有力手段。不仅如此,大部分构建工具都提供了机制让你能加载当前工程的 classpath,然后启动 REPL。这意味着你可以在 REPL 里访问工程中引用的库和你自己的代码。你能够在 REPL 里调用API 和访问远端服务器。这是很棒的快速测试Web 服务或RESTAPI 的方法,也导向我称为实验驱动开发(Experiment Driven Development)的方法。2.1.1 实验驱动开发

实验驱动开发就是开发者在写测试或生产代码前,先花点时间在交互环境或REPL 里做实验。这可以给你时间全面理解你需要打交道的软件或库的外部接口,并对其API 的优点和缺点得到点切身体会。这是学习新发布的 Web 服务或 RESTful API 或最新的Apache 库的极好办法,甚至可以用来学习你同事刚刚写出来的东西。在理解了 API 是怎么工作后,你就能更好地写自己的代码,或者开始写测试,如果你遵循测试驱动开发的话。

现在推动开发人员拥抱测试驱动开发(TDD)的呼声很高。TDD 要求开发者先写单元测试,然后写实现类。在你开始写测试前,你并不总是很清楚自己的API要定义成什么样的。TDD的一个组成部分就是通过写测试来定义API,这样你可以在(用户的)上下文里来看你的代码,可以感觉一下你自己愿意不愿意用你自己写的API。由于表达力(较差)的原因,强类型语言在应用 TDD 时可能会比动态语言碰到更多麻烦。实验驱动开发将“定义 API”这个步骤向前期推动一步,提前到了写测试代码之前。REPL 帮助开发者确保其设计的API在类型系统里能表达得出来。

Scala 是一种语法非常灵活的强类型语言,因此有时候需要用点手段欺骗类型系统才能达成你真正想要的API设计。因为很多开发者缺乏强类型理论基础,所以经常需要更多的实验。实验驱动设计(Experiment Driven Design)让你在REPL 里结合类型系统进行实验,以便为你的API提炼出最有效的类型定义。实验驱动设计主要用在给代码里添加大特性或领域对象的时候,不适合在添加新方法或者修bug时使用。

实验驱动设计在你定义领域特定语言时(DSL)也能帮上大忙。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载