Scala实用指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-21 14:26:14

点击下载

作者:(美) 文卡特·苏帕拉马尼亚姆(Venkat Subramaniam)

出版社:人民邮电出版社

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

Scala实用指南

Scala实用指南试读:

前言

很高兴见到你对Scala感兴趣。感谢你选择本书来学习和练习这门编程语言,你将感受到在一种编程语言中融合面向对象和函数式编程这两种编程范式所带来的巨大优势。

Java生态系统是目前用于开发和部署企业级应用最强大的平台之一。Java平台几乎无所不在并且用途广泛;它类库丰富,可以在多种硬件上运行,并且衍生出了200多种基于此平台的编程语言。

我有幸学过并在工作中用过十几种编程语言,而且还为其中一些写过书。我觉得,编程语言就像各种型号的汽车——它们各执所长,帮助我们掌控平台的方向。现如今,程序员能够自由选择乃至混合使用多种编程语言完成应用程序,着实令人欣喜。

典型的企业级应用受困于各种问题——烦琐的代码难以维护,可变性增加了程序出错的可能,而共享的可变状态也让并发编程的乐趣变成了炼狱。我们一再深陷主流编程语言拙劣抽象能力的泥潭中。

Scala是编译成JVM字节码的最强大的编程语言之一。它是静态类型的,简洁且富有表现力,而且它已经被各种组织用于开发高性能、具有伸缩性、即时响应性和回弹性的应用程序。

这门编程语言引入了合理的特性并规避了一些陷阱。Scala及其类库让我们能够更多地关注问题领域,而不是陷入各种底层基础设施(如多线程与同步)实现细节的泥沼之中。

Scala被设计成用于创建需要高性能、迅速响应和更具回弹性的应用。大型企业和社交媒体需要对庞大的数据进行高频的处理,Scala正是为了满足这些需求而创造的。

Scala被用于在多个领域(包括电信、社交网络、语义网和数字资产管理)中构建应用程序。Apache Camel利用Scala灵活的DSL创建路由规则。Play和Lift是两个使用Scala构建的强大的Web开发框架。Akka则是一个用Scala构建的卓越类库,用于创建具有高即时响应性、并发性的反应式应用程序。这些类库和框架都充分利用了Scala的特性,如简洁性、表现力、模式匹配和并发。

Scala是一门强大的编程语言,但我们需要专注于Scala中最有价值的关键部分,才能通过它来获得生产效率。本书旨在帮助你学习Scala的精粹,让你高效产出,完成工作,并创建实用的应用程序。

Scala提供了两种不同的编程风格,以帮助你创建实用的应用程序。  Scala的编程风格

Scala并不拘泥于一种编程风格。我们可以面向对象编程,也可以使用函数式风格,甚至可以结合两者的优点将它们混合使用。

面向对象编程是Java程序员熟悉的舒适区。Scala是面向对象和静态类型的,并在这两方面都比Java走得更远。对于初学Scala的我们,这是个好消息,因为我们在面向对象编程上多年的投入不会浪费,而是化作宝贵的经验红利。在创建传统的应用程序时,我们可以倾向于使用Scala提供的面向对象风格。我们可以像使用Java那样编写代码,利用抽象、封装、继承尤其是多态的能力。与此同时,当这些能力无法满足需求时,我们也并不受限于这种编程模型。

函数式编程风格越来越受关注,而Scala也已支持这种风格。使用Scala,我们更容易趋向不可变性,创建纯函数,降低不可预期的复杂度,并且应用函数的组合和惰性求值(lazy evaluation)策略。在函数式风格的助益下,我们可以用Scala创建高性能的单线程和多线程应用程序。Scala和其他编程语言

Scala从其他编程语言(尤其是Erlang)中借鉴了许多特性。Scala中基于Actor的并发模型就深受在Erlang中大行其道的并发模型启发。类似地,Scala中的静态类型和类型推断(type inference)也是受到别的编程语言(如Haskell)的影响。其函数式编程的能力也是借鉴了一些函数式编程的先导者们的长处。

Java 8引入了lambda表达式和强大的Stream API后(可以参考Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions[Sub14]一书),使用Java也能编写函数式风格的代码了。这对Scala或者JVM上的其他编程语言并不会是威胁,反而会缩小这些编程语言之间的隔阂,使程序员接纳或者在这些编程语言间切换变得更加轻松。

Scala能和Java生态系统无缝衔接,我们能够在Scala中使用Java类库了。我们可以完全使用Scala构建应用程序,也可以将其与Java以及JVM上的其他编程语言混合使用。于是,Scala代码既可以像脚本一样小巧,也可以像成熟的企业级应用一样庞大。谁应该阅读本书

本书的目标读者是有经验的Java程序员。我假定读者了解Java语言的语法和API。我还假定读者有丰富的面向对象编程能力。这些假定能够保证读者可以快速习得Scala的精粹并将其运用于实际的应用程序之中。

已经熟悉其他编程语言的开发者也可以使用本书,但是最好辅以一些优秀的Java图书。

已经在一定程度上熟悉Scala的程序员可以使用本书学习那些他们还没有机会探索的语言特性。熟悉Scala的程序员可以使用本书在他们的组织中培训同事。本书中包含什么

我写本书的目的是让读者能够在最短的时间内上手Scala,并使用它写出具有伸缩性、即时响应性和回弹性的应用。为了做到这一点,读者需要学习很多知识,但也有很多知识读者并不需要了解。如果读者的目的是想学习关于Scala编程语言的所有知识,那么总是会有一些知识无法在本书中找到。有其他一些关于Scala的图书在深度上做得很出色。读者在本书中学到的是那些必须了解的关键概念,目的是为了快速开始使用Scala。

我假定读者对Java相当熟悉。因此,读者无法在本书中学习到编程的基本概念。但是,我并没有假定读者已经了解了函数式编程或者Scala本身——这是读者将会在本书中学习的内容。

我写本书是为了那些忙碌的Java开发者,所以我的目的是让读者能够快速适应Scala,并尽早使用Scala来构建自己应用程序的一部分。读者将会看到书中的概念介绍节奏相当快,但是会附带大量示例。

学习一门编程语言的方式有很多,但没有比尝试示例代码(多多益善)更好的方式了。在阅读本书的同时,请键入示例代码,运行并观察结果,按照自己的思路修改它们、做各种实验、拆解并拼装代码。这将是最有趣的学习方式。本书所用的Scala版本

使用自动的脚本,本书中的代码示例使用下面的Scala版本运行过:Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).

花几分钟时间为自己的系统下载合适版本的Scala。这有助于运行本书中的代码示例(从而避免Scala版本导致的细节困扰)。线上资源

读者可以从出版社网站上本书的页面下载到所有示例代码。读者也可以提供反馈,直接提交勘误,或者在论坛上评论和提问。

下面是能够帮助读者开始阅读本书的若干网络资源:直接访问Scala的官方网站可以下载Scala。读者可以在其文档页面找到Scala标准库的文档。

让我们攀登Scala这座高峰吧。资源与支持

本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。配套资源

本书提供如下资源:● 本书源代码。

要获得以上配套资源,请在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。提交勘误

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,点击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/ selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。关于异步社区和异步图书“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。异步社区微信服务号第一部分小试牛刀本书的第一部分将帮助Java程序员更加容易地适应Scala,读者将了解:Scala提供了什么;如何创建类、元组等;如何使用REPL;Scala和Java之间有哪些差异;类型和类型推断。第1章探索Scala

Scala是一门强大的编程语言:不需要牺牲强大的静态类型检查支持,就可以写出富有表现力而又简洁的代码。

你可以使用Scala构建任意应用程序,小至小工具,大至完整的企业级应用。你可以使用熟悉的面向对象风格编程,也可以随时切换到函数式风格。Scala并不会强迫开发人员使用唯一的风格编程,开发人员可以从自己熟悉的基础开始,并在适应后,利用更多其他特性,从而使自己变得更高产,使自己的程序更高效。

让我们快速探索Scala的一些特性,然后看一看用Scala写成的一个实用示例。1.1 Scala的特性

Scala是Scalable Language的简称,是一门混合了面向对象编程的函数式编程语言。它由Martin Odersky创造,并于2003年发布了第一个版本。下面是Scala的一些关键特性:● 同时支持命令式风格和函数式风格;● 纯面向对象;● 强制合理的静态类型和类型推断;● 简洁而富有表现力;● 能和Java无缝地互操作;● 基于精小的内核构建;● 高度的伸缩性,仅用少量代码就可以创建高性能的应用程序;● 具有强大、易用的并发模型。

我们将在本书中细致地学习上面的每一个特性。1.2 以少胜多

开始接触Scala时,你将会发现Scala与Java的第一个差异是,Scala能用更少的代码做更多的事情。你写的每一行代码都充溢着Scala简洁而强大的优点。你开始使用Scala的关键特性,熟读之后,这些特性便会让你的日常编程变得相当高效——Scala简化了日常编程。

让我们快速浏览一个示例,以了解Scala的强大和优势。在这个例子中,我们将会用到很多特性。即使此刻你并不熟悉Scala语法,也请在阅读的同时输入代码并编译运行。代码写得越多,熟悉得也就越快。

如果你还没有安装Scala,可参考附录A中的步骤。下面是第一个代码示例。

Introduction/TopStock.scala1 val symbols = List("AMD", "AAPL", "AMZN", "IBM", "ORCL", "MSFT")2 val year = 20173 4 val (topStock, topPrice) =5 symbols.map { ticker => (ticker, getYearEndClosingPrice(ticker, year)) }6 .maxBy { stockPrice => stockPrice._2 }7 8 printf(s"Top stock of $year is $topStock closing at price $$$topPrice")

如果这是你第一次看到Scala代码,不要因语法分心,现阶段应专注于代码的整体结构。

这段代码从指定的股票代码列表中计算出股价最高者。让我们把这段代码拆开来逐步理解。

先看代码的主体部分。在第1行,symbols指向一个不可变的股票代码列表;在第2行,year是一个不可变的值;在第5行和第6行,使用了两个功能强大的专用迭代器——map()函数和maxBy()函数。在Java中,我们习惯用“方法”这个术语来指代类的成员,而“函数”这个术语通常用于指代不属于类的过程(procedure)。然而,在Scala中这两个术语可交换使用。

这两个迭代器分别行使了两种独立的职责。首先,我们使用map()函数遍历股票代码,以创建一个由股票代码及其2017年收盘价格组成的“对”或“元组”为元素的列表。最终结果的元组列表形式为List((股票代码1,价格1),(股票代码2,价格2),...)。

第二个迭代器处理第一个迭代器的结果。maxBy()函数是一个从列表中取出最大值的专用迭代器。因为该列表中的值是元组(对),所以我们需要告诉maxBy()函数如何比较两个值。在maxBy()函数附带的代码块中,我们指定了一个包含两个元素的元组,我们感兴趣的是第二个属性(代码块中的_2)——价格。这段代码十分简洁,但却做了不少事情。图1-1将这些动作进行了可视化。图1-1

如图1-1所示,map()函数将指定的函数或者操作(在这里是获取价格)应用到每一个股票代码上,并创建一个以股票代码及其价格为元素的结果列表;然后maxBy()函数在结果列表上计算得到价格最高的股票代码。

上述代码没有给出getYearEndClosingPrice()函数,接下来我们来看一看它。

Introduction/TopStock.scalacase class Record(year: Int, month: Int, date: Int, closePrice: BigDecimal)def getYearEndClosingPrice(symbol: String, year: Int): BigDecimal = { val url = s"https://raw.githubusercontent.com/ReactivePlatform/" + s"Pragmatic-Scala-StaticResources/master/src/main/resources/" + s"stocks/daily/daily_$symbol.csv" val data = io.Source.fromURL(url).mkString val maxClosePrize = data.split("\n") .filter(record => record.startsWith(s"$year-12")) .map(record => { val Array(timestamp, open, high, low, close, volume) = record.split(",") val Array(year, month, date) = timestamp.split("-") Record(year.toInt, month.toInt, date.toInt, BigDecimal(close.trim)) }) .sortBy(_.date)(Ordering[Int].reverse) .take(1) .map(_.closePrice) .head maxClosePrize}

即使你现在还不熟悉语法,这段代码也应该很容易阅读。在这个简短而亲切的函数中,我们向Web服务发送请求,并收到CSV格式的股票数据。然后我们解析这些数据,提取并返回年终收盘价。现在不用在意接收到的数据的格式,它对我们在这里所关注的焦点而言并不重要。在第15章中,我们将重温这个例子,并提供与Web服务通信相关的所有细节。

要运行前面的例子,可以将上述两段代码保存到一个名为TopStock.scala的文件中,并且使用以下命令:scala TopStock.scala

将会看到这样的结果:Top stock of 2017 is AMZN closing at price $1169.4700

花几分钟时间研读这段代码,以确保自己了解这是如何工作的。在研究代码的过程中,要查看该方法是如何做到计算出最高价格而又无须显式更改任何变量或对象的。这整段代码完全只处理不可变的状态,一旦创建,没有变量或对象会被更改。因此,如果你要并行运行这段代码,不必担心任何同步和数据竞争问题。

我们已经从网络上获取了数据,做了一些比较,并产生了所需的结果——这是非常重要的工作,但它只需要几行代码。即使我们新增一些需求,这段Scala代码还是能保持简洁且富有表现力。让我们来看一看。

在这个例子中,我们从Web获取每个股票代码的数据,这涉及多次访问网络的调用。假设网络延迟是d秒,而我们要分析n支股票,那么顺序代码大概需要n × d秒。因为代码中最大的延迟在于访问网络来获取数据,所以如果我们并行地执行代码以获取不同股票代码的数据,那么我们可以将时间缩短到大约d秒。Scala使得将顺序代码改成并行模式变得很简单,只需一个很小的改动:symbols.par.map { ticker => (ticker, getYearEndClosingPrice(ticker, year)) } .maxBy { stockPrice => stockPrice._2 }

我们插入了对par的调用,就是这么简单。这段代码现在已经是在并行地处理每一个股票代码,而不是顺序迭代。

我们来强调一下这个例子的一些优点。● 首先,这段代码很简洁。我们利用了Scala的许多强大特性,如

函数值、(并行)集合、专用迭代器、不可变值、不可变性和元

组等。当然,我还没有介绍这些概念,只是简单提及!所以,不

要试图在这一刻就理解所有这一切,在本书的其余部分,我们将

会娓娓道来。● 我们使用了函数式风格,具体说来就是函数组合。我们使用

map()方法将股票代码的列表转换为股票代码及其价格组成的元

组的列表。然后我们使用maxBy()方法将其转换成所需的值。和

使用命令式风格不同,我们将控制逻辑让渡给函数所在的标准库

以完成任务,而不是耗费精力在迭代的控制上。● 无痛地使用并发。没有必要再使用wait()和notify()方法或者

synchronized关键字了。因为我们只处理不可变的状态,所以就

不必耗费时间或者精力(甚至无数的不眠之夜)来处理数据竞争

和同步。

这些优点已经让我们肩头的负担减轻不少。例如,我们几乎不费吹灰之力就将代码并发化了。有关线程多么令人头疼的详尽论述,参考Brian Goetz的Java Concurrency in Practice[Goe06]一书。使用Scala可以专注于应用程序逻辑,而不用关心底层。

我们看到了Scala在并发上的优势。与此同时,Scala也为单线程应用提供了诸多便利。Scala赋予了我们自由,让我们可以随心选择或者同时混合使用命令式风格和无赋值操作的纯函数式风格。在Scala中,有了混用这两种风格的能力,我们就可以在单线程作用域中使用最合适的风格。而对于多线程或并发安全问题,我们倾向于使用函数式风格。

Java中的原始类型在Scala中被看作对象。例如,2.toString()在Java中将产生编译错误,但在Scala中是有效的——我们在Int的实例上调用了toString()方法。同时,为了提供良好的性能以及与Java互操作能力,在字节码级别上,Scala将Int的实例映射到int的表示上。

Scala编译成了字节码,这样我们就可以使用运行Java程序的方式来运行Scala程序,也可以用脚本的方式运行它。Scala也可以很好地与Java互操作。我们可以从Scala类扩展出Java类,反之亦然。我们也可以在Scala中使用Java类,或者在Java中使用Scala类。我们甚至可以混合使用多种编程语言,成为真正的多编程语言程序员。

Scala是一门静态类型的编程语言,但与Java不同,它的静态类型更加合理——Scala会尽可能地使用类型推断。我们可以依靠Scala本身来推断出类型,并将结果类型应用到其余代码中,而不是重复又冗余地指定类型。我们不应该为编译器工作,而应该让编译器为我们工作。例如,当我们定义var i = 1时,Scala将立即推断出变量i的类型为Int。如果此时我们尝试将一个字符串赋值给这个变量,如i = "haha",那么Scala就会抛出错误信息,如下所示:sample.scala:2:error: type mismatch; found : String("haha") required: Inti = "haha" // 编译错误 ^one error found

在本书后面,我们将看到类型推断是如何在这种简单定义以及函数参数和返回值上起作用的。

Scala提倡简洁。在语句结尾放置一个分号是Java程序员的习惯。而Scala解放了程序员的右手小拇指——句末分号在Scala中是可选的。而且,这只是一个开始。在Scala中,根据上下文,点操作符(.)和括号都是可选的。因此,我们可以编写s1 equals s2来替代s1.equals(s2)。通过去除分号、点号和括号,代码获得了较高的信噪比,使得编写领域特定语言变得更加容易。

Scala最有趣的特点之一是伸缩性。我们可以享受到函数式编程构造与功能强大的库之间的良好互操作性,以创建高度伸缩性的并发应用程序,并充分利用多核处理器上的多线程能力。

Scala真正的美在于它的精简。与Java、C#和C++相比,Scala中内置的核心规则十分精简。剩下的部分,包括操作符,都属于Scala标准库。这种区别影响深远。因为Scala本身做得越少,所以我们就可以发挥越多。它具有极佳的扩展性,它的标准库就是一个样例。

这一节中的代码展示了,我们仅用几行代码就可以完成许多任务。这种简洁性部分来源于函数式编程的声明式风格——接下来让我们对此做更进一步的研究。1.3 函数式编程

函数式编程(Functional programming,FP)已经存在了数十年,但是它终于获得了很大的关注。如果你主要使用面向对象编程(Object Oriented Programming,OOP),那么你得付出一些努力才能适应函数式编程,而Scala能将这种负担减轻不少。

Scala本质上是一门混合型编程语言,我们既可以使用命令式风格也可以使用函数式风格,这是把双刃剑。其优点在于,当使用Scala编写代码时,我们可以先使其工作,然后再做优化。对于刚刚接触函数式编程的程序员,他们可以先用命令式风格写好代码,然后再将代码重构成函数式风格。另外,如果一个特定的算法使用命令式风格实现真的比较好,那么很容易就可以用Scala编写出来。然而,如果团队滥用这种灵活性,随意组合编程范式的话,那么这种灵活性就可能会变成一种诅咒。首席开发人员的仔细监督对于帮助维护一种适合团队的一致编程风格可能是必要的。

函数式编程提倡不可变性、高阶函数和函数组合。这些特性合在一起就能使代码简洁、富有表现力、易于理解和修改。不可变性还有助于减少那些由于状态改变而悄然滋生的错误。

让我们花几分钟,通过和命令式风格的代码对比来获得对函数式编程的感觉。

下面是一段命令式风格的Java代码,用于从给定日期开始的一系列温度中计算出最大值:// Java代码public static int findMax(List temperatures) { int highTemperature = Integer.MIN_VALUE; for(int temperature : temperatures) { highTemperature = Math.max(highTemperature, temperature); } return highTemperature;}

Scala也支持命令式风格,下面是Scala版本的代码。

Introduction/FindMaxImperative.scaladef findMax(temperatures: List[Int]) = { var highTemperature = Integer.MIN_VALUE for (temperature <- temperatures) { highTemperature = Math.max(highTemperature, temperature) } highTemperature}

我们创建了可变变量highTemperature,并在循环中持续修改它。我们必须确保正确地初始化可变变量,并在正确的地方把它们修改为正确的值。

函数式编程是一种声明式风格,我们只要指定做什么而不用指定如何去做。XSLT、规则引擎和ANTLR这些工具都普遍使用声明式风格。让我们把前面的代码用不带可变参数的函数式风格重写一下。

Introduction/FindMaxFunctional.scaladef findMax(temperatures: List[Int]) = { temperatures.foldLeft(Integer.MIN_VALUE) { Math.max }}

上面的代码体现了Scala的简洁性和函数式编程风格。

我们创建了一个以不可变温度值集合作为参数的函数findMax()。在括号和左大括号之间的=告诉Scala去推断这个函数的返回类型,在本例中是Int。

在这个函数中,集合的foldLeft()方法在集合的每一个元素上应用函数Math.max()。java.lang.Math类的max()方法接受两个参数,并算出两者中的较大者。这两个参数在前面的代码中被隐式传递。max()方法的第一个隐式参数是前一次计算出的最高值,而第二个参数是集合在被foldLeft()方法遍历时的当前元素。foldLeft()方法取出调用max()方法的结果,也就是当前最高值,并将它传递到下一次对max()方法的调用中,以便和下一个元素进行比较。foldLeft()方法的参数是最高温度的初始值。

图1-2用一些温度的样本值帮助我们将这个例子中findMax()函数的作用机制可视化。

图1-2展示了findMax()函数调用foldLeft()方法并作用到temperatures列表上的过程。foldLeft()方法首先将给定的函数Math.max()作用于初始值Integer.MIN_VALUE和列表中的第一个元素23。两者中的较大者23将会和列表中的第二个值一起被传入Math.max()方法。这次计算的结果是27,也就是两者中的较大值,它将和列表中的最后一个值相比较,还是使用Math.max()方法。在方框中的这些操作序列就是foldLeft()方法的内部作用机制。findMax()方法最终将返回foldLeft()方法产生的结果。图1-2

这个示例中的代码相当密集,需要花几分钟深入学习。

foldLeft()方法需要费一些气力才能掌握——让我们通过另一个心算练习来理解它。做一个一分钟的假设,把集合中的元素看成排成一行的人,我们要算出其中最年长者的年龄。我们在一张纸条上写上0并交给该行上的第一个人。第一个人扔掉纸条(因为他的年龄比0大),并在一个新纸条上写下他的年龄20,然后把纸条传递给下一个人。第二个人的年龄比20小,所以他只需把纸条传递给下一个人。第三个人的年龄是32,他扔掉纸条并新写一张继续传递下去。我们从该行上最后那个人的纸条上获得的就是最年长者的年龄。将这个计算动作序列可视化将有助于了解foldLeft()内部的作用机制。

看前面代码的感觉就像把红牛一饮而尽。Scala代码高度简洁而紧凑,你必须花费一些精力来学习。一旦这样做了,你就能从它强大的功能和丰富的表现力中受益。

我们来看一看函数式风格的另一个例子。假设我们想要一个列表,其元素是原始列表中的值的两倍。要实现这个功能,我们只需发出让所有元素都翻倍的指令,让Scala自己做循环即可,而不是循环遍历每一个元素,如下所示。

Introduction/DoubleValues.scalaval values = List(1, 2, 3, 4, 5)val doubleValues = values.map(_ * 2)

关键字val可读作不可变的(immutable)。它告诉Scala,变量values和doubleValues一旦创建就不能更改。

虽然看起来不像一个函数,但是_ * 2就是一个函数。它是一个匿名函数(anonymous function),也就是说一个只有主体但没有名字的函数。下划线(_)表示传递给此函数的参数。函数本身作为参数值传递给了map()函数。map()函数迭代遍历集合,并把集合中的每一个元素作为参数值来调用匿名函数。总体的结果就是:一个由元素值是原始列表中元素值的两倍的元素所组成的新列表。

函数是Scala中的一等公民,这也解释了为什么我们能够把函数当作常规的参数和变量,在这个例子中,函数就是求一个数的两倍。

虽然我们获得了一个列表,其中元素的值都是原始列表中元素值的两倍,但是我们并没有修改任何变量或对象。这种不可变的方式是让函数式编程成为并发编程的理想编程风格的关键。在函数式编程中,函数是纯的,它们产生的输出只依赖于它们所接收到的输入,并且它们不会影响任何全局和局部变量的状态,也不会受任何全局或局部的状态影响。

编程中采用不可变性有明显的好处,但是复制对象而不是改变它们难道不会导致性能糟糕并增加内存使用吗?如果不小心,确实会这样。但是Scala依赖于一些特殊的数据结构来提供良好的性能和高效的内存使用。例如,Scala列表是不可变的,因此复制一个列表以在列表的头部增加一个额外的元素将复用已经存在的列表。因此,复制一个列表以将元素插入到列表的开头,在时间复杂度和空间复杂度上都是O(1)。同样,Scala的Vector用一种名为Tries的特殊不可变数据结构实现。在设计上,它就能够高效复制集合,以常数级的时间复杂度和空间复杂度来改变集合中的任意元素。1.4 小结

通过本章中的几个示例,我们得以一窥Scala高度简洁和富有表现力的本质。这些简短的例子以走马观花的方式介绍了几个特性,包括函数式风格、易用的并发、集合的使用、酷炫的迭代器、不可变性编程、元组的使用。我们学习了变量和值的定义、静态类型检查以及类型推断。最重要的是,我们也看到了Scala是多么简洁且富有表现力。

我们已经快速接触了一些概念,在本书的其余部分,我们将对这其中的每一个概念都做更加深入的探讨。在下一章中我们将开始编译并运行Scala代码。第2章体验Scala

令人惊喜的是,无论是创建一个简短的脚本还是一个完整的企业级应用,都可以轻松地用Scala代码实现并运行。你可以使用任何IDE,也可以只使用轻量级的编辑器。

在本章中,我们将学习如何从命令行快速运行Scala脚本以及如何编译包含Scala代码的多个文件。如果你想探究Scala的运行机制,例如,推断出来的变量类型是什么,那么你随时可以快速跳入REPL,Scala将以交互的方式显示有用的细节。没有比通过尝试一些例子来学习Scala更好的方法了,所以请在阅读的同时输入代码并运行。让我们开始使用这个最有意思的交互式工具——REPL。2.1 使用REPL

相当多的编程语言都提供了REPL(read-eval-print loop)工具,使用REPL可以便捷地键入代码片段,并以交互方式立即看到代码运行结果。除了执行代码片段外,REPL往往还提供一些在运行时不方便获取的细节。这使得REPL成为一个特殊工具,可以用来做试验,也可以用来学习Scala推断变量或函数类型的方法。

名为scala的命令就是Scala的REPL,也是尝试这种编程语言最快捷的方式。使用这个工具我们就可以开始把玩小巧的代码片段。它不仅仅是一个学习工具,在大型应用的开发中也非常有用。你可以在REPL中快速尝试一些代码原型,然后使用世界上最好的技术——复制和粘贴——从REPL复制代码到自己的应用程序中。

要启动REPL,应在命令行(在终端窗口或命令提示符下)键入scala。启动后会打印出一些介绍信息,紧跟着一个提示符:Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).Type in expressions for evaluation. Or try :help.scala>

在提示符下,键入val number = 6,然后按下回车键。Scala shell会响应,表明它根据赋给它的值6推断出变量number是Int类型的:scala> val number = 6number: Int = 6

现在尝试重新给number赋值,Scala会反馈如下错误:scala> number = 7:11: error: reassignment to val number = 7 ^

Scala提示说不能对不可变变量number进行重新赋值。但是在控制台中,可以重新定义不可变变量和可变变量。例如,shell会默默地接受以下内容:scala> val number = 7number: Int = 7

在同一个作用域中重新定义不可变变量和可变变量只在交互式的shell中可行,在真实的Scala代码和脚本中行不通——这种灵活性使在shell中做试验很方便,同时,在应用程序代码中规避错误。

我们已经看到Scala如何推断出类型为Int。让我们看另外一个例子,在本例中变量的类型被推断为List。scala> val list = List(1, 2, 3)list: List[Int] = List(1, 2, 3)

在编写应用程序代码的任何时候,如果不确定表达式会被推断成什么(类型),都可以快速在shell中尝试。

在shell中,使用向上箭头可以唤出上一次键入的命令。我们甚至可以在shell中找回之前启动shell时使用过的命令。

在输入一行命令时,按Ctrl+A可以转到行首,按Ctrl+E可以转到行尾。

只要收到回车键,shell就会执行所输入的内容。如果没有完整输入然后按下回车键,例如,在写一个方法定义的过程中,shell就会显示竖线(|)提示输入更多代码。例如,让我们在两行上定义一个方法isPalindrome(),然后两次调用这个方法并查看结果:scala> def isPalindrome(str: String) = | str == str.reverseisPalindrome: (str: String)Booleanscala> isPalindrome("mom")res0: Boolean = truescala> isPalindrome("dude")res1: Boolean = falsescala> :quit

键入:quit退出shell。

除了键入所有代码,还可以使用:load选项从文件加载代码到shell中。例如,要加载名为script.scala的文件,应键入:load script.scala。在加载那些已经预先写好的函数和类,并在交互模式下做试验时,这个选项非常有用。

使用shell能方便地对小段代码做试验,你很快就会找到运行保存在文件中的代码的简易方法——你将在下一节中习得相关知识。

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载