1.2 何为Scala
Scala,是Scalable Language的缩写,它是一门混合型的函数式编程语言。Martin Odersky③是它的创始人,2003年发布了第一个版本。下面是Scala的一些关键特性④:
③请阅读附录A,了解更多信息。
④请参考附录A,获得权威的语言规范。
它拥有基于事件的并发模型;
它既支持命令式风格,也支持函数式风格;
它是纯面向对象的;
它可以很好的与Java混合;
它强制使用自适应静态类型;
它简洁而有表现力;
它构建于一个微内核之上;
它高度可扩展,可以用更少的代码创建高性能应用。
下面的小例子突出了这些特性:
Introduction/TopStock.scala
import scala.actors._
import Actor._
val symbols = List( "AAPL", "GOOG", "IBM", "JAVA", "MSFT")
val receiver = self
val year = 2008
symbols.foreach { symbol =>
actor { receiver ! getYearEndClosing(symbol, year) }
}
val (topStock, highestPrice) = getTopStock(symbols.length)
printf("Top stock of %d is %s closing at price %f\n", year, topStock, highestPrice)
不用想语法,我们先从大处着眼。symbols
指向一个不变的List
,其中持有股票代码。我们对这些股票代码进行循环,调用actor
。每个actor
在单独的线程中执行。因此,同actor
关联的代码块({}
)运行在其自己的线程上。它调用(尚未实现的)函数getYearEndClosing()
。这个调用的结果返回发起请求的actor
。这由特殊的符号(!
)实现。回到主线程,我们调用(尚未实现的)函数getTopStock()
。在上面的代码完全实现之后,我们就可以并发地查询股票收盘价了。
现在,我们看看函数getYearEndClosing()
:
Introduction/TopStock.scala
def getYearEndClosing(symbol : String, year : Int) = {
val url = "http://ichart.finance.yahoo.com/table.csv?s=" +
symbol + "&a=11&b=01&c=" + year + "&d=11&e=31&f=" + year + "&g=m"
val data = io.Source.fromURL(url).mkString
val price = data.split("\n")(1).split(",")(4).toDouble
(symbol, price)
}
在这个短小可爱的函数里面,我们向http://ichart.finance.yahoo.com发出了一个请求,收到了以CSV格式返回的股票数据。我们解析这些数据,提取年终收盘价。现在,先不必为收到数据的格式操心,它并不是我们要关注的重点。在第14章,我们还将再用到这个例子,提供所有与Yahoo服务交流的细节。
还需要实现getTopStock()
方法。在这个方法里,我们会收到收盘价,确定最高价的股票。我们看看如何用函数式风格实现它:
Introduction/TopStock.scala
def getTopStock(count : Int) : (String, Double) = {
(1 to count).foldLeft("", 0.0) { (previousHigh, index) =>
receiveWithin(10000) {
case (symbol : String, price : Double) =>
if (price > previousHigh._2) (symbol, price) else previousHigh
}
}
}
在这个getTopStock()
方法中,没有对任何变量进行显式赋值的操作。我们以股票代码的数量作为这个方法的参数。我们的目标是找到收盘价最高的股票代码。因此,我们把初始的股票代码和高价设置为("", 0.0
),以此作为foldLeft()
方法的参数。我们用foldLeft()
方法去辅助比较每个股票的价格,确定最高价。通过receiveWithin()
方法,我们接收来自开始那个actor
的股票代码和价格。如果在指定时间间隔没有收到任何消息,receiveWithin()
方法就会超时。一收到消息,我们就会判断收到的价格是否高于我们当前的高价。如果是,就用新的股票代码及其价格作为高价,与下一次接收的价格进行比较。否则,我们使用之前确定的(previousHigh
)股票代码和高价。无论从附着于foldLeft()
的代码块(code block)中返回什么,它都会作为参数,用于在下一元素的上下文中调用代码块。最终,股票代码和高价从foldLeft()
返回。再强调一次,从大处着眼,不要管这里的方法的细节。随着学习的深入,你会逐步了解它们的详细内容。
大约25行代码,并发地访问Web,分析选定股票的收盘价。花上几分钟,分析一下代码,确保你理解了它是如何运作的。重点看方法是如何在不改变变量或对象的情况下,计算最高价的。整个代码只处理了不变状态;变量或对象在创建后就没有修改。其结果是,你不需要顾虑同步和数据竞争,代码也不需要有显式的通知和等待序列。消息的发送和接收隐式地处理了这些问题。
如果你把上面所有的代码放到一起,执行,你会得到如下输出:
Top stock of 2008 is GOOG closing at price 307.650000
假设网络延迟是d秒,需要分析的是n个股票代码。如果编写代码是顺序运行,大约要花n×d秒。因为我们并行执行数据请求,上面的代码只要花大约d秒即可。代码中最大的延迟会是网络访问,这里我们并行地执行它们,但并不需要写太多代码,花太多精力。
想象一下,用Java实现上面的例子,你会怎么做。
上面的代码的实现方式与Java截然不同,这主要体现在下面3个方面。
首先,代码简洁。Scala一些强大的特性包括:actor、闭包、容器(collection)、模式匹配、元组(tuple),而我们的示例就利用了其中几个。当然,我还没有介绍过它们,这还只是简介!因此,不必在此刻就试图理解一切,通读本书之后,你就能够理解它们了。
我们使用消息进行线程间通信。因此不再需要
wait()
和notify()
。如果你使用传统Java线程API,代码会复杂几个数量级。新的Java并发API通过使用executor服务减轻了我们的负担。不过,相比之下,你会发现Scala基于actor的消息模型简单易用得多。因为我们只处理不变状态,所以不必为数据竞争和同步花时间或精力(还有不眠夜)。
这些益处为你卸下了沉重的负担。要详细地了解使用线程到底有多痛苦,请参考Brian Goetz的Java Concurrency in Practice [Goe06]。运用Scala,你可以专注于你的应用逻辑,而不必为低层的线程操心。
你看到了Scala并发的益处。Scala也并发地⑤为单线程应用提供了益处。Scala让你拥有选择和混合两种编程风格的自由:Java所用的命令式风格和无赋值的纯函数式风格。Scala允许混合这两种风格,这样,你可以在一个线程范围内使用你最舒服的风格。Scala使你能够调用和混合已有的Java代码。
⑤这里一语双关。——编者注
在Scala里,一切皆对象。比如,2.toString()
在Java里会产生编译错误。然而,在Scala里,这是有效的——我们调用Int
实例的toString()
方法。同时,为了能给Java提供良好性能和互操作性,在字节码层面上,Scala将Int
的实例映射为32位的基本类型int
。
Scala编译为字节码。你可以按照运行Java语言程序相同的方式运行它。⑥也可以很好的将它同Java混合起来。你可以用Scala类扩展Java类,反之亦然。你也可以在Scala里使用Java类,在Java里使用Scala类。你可以用多种语言编写应用,成为真正的多语言程序员⑦——在Java应用里,在需要并发和简洁的地方,就用Scala(比如创造领域特定语言)吧!
⑥你可以把它当作脚本运行。
⑦参见附录A,也请阅读Neal Ford著的The Productive Programmer [For08]。
Scala是一个静态类型语言,但是,不同于Java,它拥有自适应的静态类型。Scala在力所能及的地方使用类型推演。因此,你不必重复而冗繁地指定类型,而可以依赖语言来了解类型,在代码的剩余部分强制执行。不是你为编译器工作;相反,编译器为你工作。比如,我们定义var i = 1
,Scala立即就能推演出变量i
是Int
类型。现在,如果我们将某个字符串赋给那个变量,比如,i = "haha"
,编译器就会给出如下的错误:
error: type mismatch;
found : java.lang.String("haha")
required: Int
i= "haha"
在本书后面,你会看到类型推演超越了简单类型定义,也进一步超越了函数参数和返回值。
Scala偏爱简洁。在语句结尾放置分号是Java程序的第二天性。Scala可以为你的小拇指能从多年的虐待中提供一个喘息之机——分号在Scala中是可选的。但是,这只是个开始。在Scala中,根据上下文,点运算符(.
)也是可选的,括号也是。因此,不用写成s1.equals(s2);
,我们可以这么写s1 equals s2
。去掉了分号、括号和点,代码会有一个高信噪比。它会变成更易编写的领域特定语言。
Scala最有趣的一个方面是可扩展性。你可以很好享受到函数式编程构造和强大的Java程序库之间的相互作用,创建高度可扩展的、并发的Java应用,运用Scala提供的功能,充分发挥多核处理器的多线程优势。
Scala真正的魅力在于它内置规则极少。相比于Java,C#和C++,Scala语言只内置了一套非常小的内核规则。其余的,包括运算符,都是Scala程序库的一部分。这种差异具有深远的影响。因为语言少做一些,你就能用它多做一些。这是真正的可扩展,它的程序库就是一个很好的研究案例。