ScalaTutorial

ScalaTutorial
ScalaTutorial

A Scala T utorial

for Java pr ogrammer s

Version 1.3

March 15, 2009

Michel Schinz, Philipp

Haller

Translated to Simplified

Chinese by Bearice

P R O G R A M M I N G M E T H O D S L A B O R A T O R Y

E P

F L

S W I T Z E R L A N D

1 Intr oduction

简介

This document gives a quick introduction to the Scala language and compiler. I t is intended for people who already have some programming experience and w a nt an overview of what they can do with Scala. A basic knowledge of obje c t-orient e d programming, especially in Java, is ass u med.

本文仅在对Scala语言和其编译器进行简要介绍。本文的目的读者是那些已经具有一定编程经验,而想尝试一下Scala语言的人们。要阅读本文,你应当具有基础的面向对象编程的概念,尤其是Java语言的。

2 A first e xample

第一个例子

As a first example, we will use the standard Hello world program. It is not very fas ci- nating but makes it easy to demonstrate the use of the Scala tools without kno wing too much about the language. Here is how it l ooks:

作为学习Scala的第一步,我们将首先写一个标准的HelloWorld,这个虽然不是很有趣,但是它可以让你对Scala有一个最直观的认识而不需要太多关于这个语言的知识。我们的Hello world看起来像这样:

object HelloWorld {

def main(args: Array[String]) {

println("Hello, world!")

}

}

The structure of this program should be familiar to Java programmers: it c o n sists of one method called main which takes the command line arguments, an array of strings, as pa r am e t er;the body of this method consists of a single call to the p re- defined method println with the friendly greeting as argument. The main method does not return a value (it is a procedure method). Therefore, it is not necessary t o declare a return type.

程序的结构对Java程序员来说可能很令人怀念:它由一个main函数来接受命令行参数,也就是一个String数组。这个函数的唯一一行代码把我们的问候语传递给了一个叫println的预定义函数。main函数不返回值(所以它是一个procedure method)。所以,也不需要声明返回类型。

What is less familiar to Java programmers is the object declaration containing th e main method. Such a declaration introduces what is commonly known as a si ngle- ton object, that is a class with a single instance. The declaration above thus declares both a class called HelloWorld and an instance of that class, also called HelloWorld. This instance is created on demand, the first time it is u sed.

对于Java程序员比较陌生的是包含了main函数的object语句。这样的语句定义了一个单例对象:一个有且仅有一个实例的类。object语句在定义了一个叫HelloWorld的类的同时还定义了一个叫HelloWorld的实例。这个实例在第一次使用的时候会进行实例化。

The astute reader might have noticed that the main method is not declared as static here. This is because static members (methods or fields) do not exist in Scala. R ath er than defining static members, the Scala programmer declares these members in singleton object s.

聪明的读者可能会发现main函数并没有使用static修饰符,这是由于静态成员(方法或者变量)在Scala中并不存在。Scala从不定义静态成员,而通过定义单例object取而代之。

2.1 Compiling the e xample

编译实例

To compile the example, we use scalac, the Scala compiler. scalac works like most compilers: it takes a source file as argument, maybe some options, and pr odu c e s one or several object files. The object files it produces are standard Java class files.

我们使用Scala编译器“scalac”来编译Scala代码。和大多数编译器一样,scalac 接受源文件名和一些选项作为参数,生成一个或者多个目标文件。scala编译生成的产物就是标准的Java类文件。

If we save the above program in a file called HelloWorld.scala, we can compile it by issuing the following command (the greater-than sign …>?represents the sh e ll prompt and should not be t y p e d):

假设我们吧上述代码保存为文件HelloWorld.scala,我们使用下面的命令编译它(大于号“>”表示命令提示符,你不必输入它)

> scalac HelloWorld.scala

This will generate a few class files in the current directory. One of them will be calle d HelloWorld.class, and contains a class which can be directly executed using th e scala command, as the following section sh o ws.

这将会在当前目录生成一系列.class文件。其中的一个名为HelloWorld.class 的文

件中定义了一个可以直接使用scala命令执行的类。下文中你可以看到这个例子。

2.2 Running the e xample

运行实例

Once compiled, a Scala program can be run using the scala command. Its usage is very similar to the java command used to run Java programs, and accepts the sa me options. The above example can be executed using the following command, which produces the expected o utp u t:

一旦完成编译,Scala程序就可以使用scala命令执行了。scala的用法和java很相似,并且连选项也大致相同。上面的例子就可以使用下面的命令运行,这将会产生我们所期望的输出。

> scala -classpath . HelloWorld

Hello, world!

3 Interaction with Java

与Java交互

One of Scala?s strengths is that it makes it very easy to interact with Java code. All classes from the https://www.360docs.net/doc/473957458.html,ng package are imported by default, while others need to be imported explicitly.

Scala的一个强项在于可以很简单的于已有的Java代码交互,所有https://www.360docs.net/doc/473957458.html,ng中的类都已经被自动导入了,而其他的类需要显式声明导入。

Le t?s look at an example that demonstrates this. We want to obtain and format th e current date according to the conventions used in a specific country, say F r anc e1.

来看看演示代码吧。我们希望对日期进行格式化处理,比如说法国。

J av a?s class libraries define powerful utility classes, such as Date and DateFormat. Since Scala interoperates seemlessly with Java, there is no need to implement equi v- alent classes in the Scala class library–we can simply import the classes of the c or- responding Java p acka ges:

Java类库定义了一系列很有用的类,比如Date和DateFormat。由于Scala于Java能够进行很好的交互,我们不需要在Scala类库中实现等效的代码,而只需直接吧Java的相关类导入就可以了:

import java.util.{Date, Locale}

import java.text.DateFormat

import java.text.DateFormat._

object FrenchDate {

def main(args: Array[String]) {

val now = new Date

val df = getDateInstance(LONG, Locale.FRANCE)

println(df format now)

}

}

Scala?s import statement looks very similar to J av a?s equivalent, however, it is mor e powerful. Multiple classes can be imported from the same package by e n closing them in curly braces as on the first line. A not her difference is that when impor t ing all the names of a package or class, one uses the underscore character (_) instead of the asterisk (*). Th at?s because the asterisk is a valid Scala identifier (e.g. method name), as we will see later.

Scala的import语句看上去与Java的非常相似,但是它更加强大。你可以使用大括号来导入同一个包里的多个类,就像上面代码中第一行所做的那样。另一个不同点是当导入一个包中所有的类或者符号时,你应该使用下划线(_)而不是星号(*)。这是由于星号在Scala中是一个有效的标识符(例如作为方法名称)。这个例子我们稍后会遇到。

The import statement on the third line therefore imports all members of the

DateFormat class. This makes the static method getDateInstance and the static field LONG di- rectly visibl e.

第三行的import语句导入了DataFormat类中的所有成员,这使得静态方法

getDateInstance和静态变量LONG可以被直接引用。

Inside the main method we first create an instance of J av a?s Date class which b y default contains the current date. Next, we define a date format using the st at i c getDateInstance method that we imported previously. Finally, we print the curr en t date formatted according to the localized DateFormat instance. This last line s ho ws an interesting property of Sca l a?s syntax. Methods taking one argument can be u s e d with an infix syntax. That is, the express i on

在main函数中,我们首先建立了一个Java的Date实例。这个实例默认会包含当前时间。接下来我们一个使用刚才导入的静态函数getDateInstance定义了日期格式。最后我们将使用DataFotmat格式化好的日期打印了出来。最后一行代码显示了Scala的一个有趣的语法:只有一个参数的函数可以使用(××××)。这样一来,我们的表达式:

df format now

is just another, slightly less verbose way of writing the

expr ession

其实就是下面的这个冗长的表达式的简洁写法

df.format(now)

This might seem like a minor syntactic detail, but it has important c onsequ en ces, one of which will be explored in the next section.

这看起来是一个语法细节,但是它具有一个重要的后果,我们将在下一节进行说明。

To conclude this section about integration with Java, it should be noted that it is also possible to inherit from Java classes and implement Java interfaces directly in S cala.

总结一下与Java的交互性,我们应当注意到Scala中可以直接继承或者实现Java中的接口和类。

4 Everything is an

object 万物皆对象

Scala is a pure object-oriented language in the sense that everything is an obje c t, including numbers or functions. It differs from Java in that respect, since Java dis- tinguishes primitive types (such as boolean and int) from reference types, and does not enable one to manipulate f u n ct i on s as v a l ues. Scala作为一个纯面向对象的语言,于是在Scala中万物皆对象,包括数字和函数。在这方面,Scala于Java存在很大不同:Java区分原生类型(比如boolean和int)和引用类型,并且不能吧函数当初变量操纵。

4.1 Number s are

objects 数字和对象

Since numbers are objects, they also have methods. And in fact, an arithmetic ex- pression like the f ollo wi n g:

由于数字本身就是对象,所以他们也有方法。事实上我们平时使用的算数表达式(如下例)

1 +

2 *

3 / x

consists exclusively of method calls, because it is equivalent to the following exp res- sion, as we saw in the previous section:

是由方法调用组成的。它等效于下面的表达式,我们在上一节见过这个描述。

(1).+(((2).*(3))./(x))

This also means that +, *, etc. are valid identifiers in Scala.

这也意味着 +,-,*,/ 在Scala中也是有效的名称。

The parentheses around the numbers in the second version are necessary because Scala?s lexer uses a longest match rule for tokens. Therefore, it would break the f o l- lowing expression:

在第二个表达式中的这些括号是必须的,因为Scala的分词器使用最长规则来进行分词。所以他会把下面的表达式:

1.+(2)

into the tokens 1., +, and 2. The reason that this tokenization is chosen is because 1. is a longer valid match than 1. The token 1. is interpreted as the literal 1.0, makin g it a Double rather than an Int. Writing the expression as:

理解成表达项1. ,+,和2的组合。这样的组合结果是由于1.是一个有效的表达项并且比表达项1要长,表达项1.会被当作1.0 ,使得它成为一个double而不是int。而下面的表达式阻止了分析器错误的理解

(1).+(2)

prevents 1 from being interpreted as a Double.

4.2 Functions are objects

函数与对象

Perhaps more surprising for the Java programmer, f u n ct i on s are also objects in Sca l a. It is therefore possible to pass functions as arguments, to store them in v ar iables, and to return them from other functions. This ability to manipulate f u n ct i on s as values is one of the cornerstone of a very interesting programming paradigm calle d functional pr ogr ammi ng.

也许对于Java程序员来说这比较令人惊讶,函数在Scala语言里面也是一个对象。于是吧函数作为参数进行传递、把它们存贮在变量中、或者当作另一个函数的返回值都是可能的。吧函数当成值进行操作是函数型编程语言的基石。

As a very simple example of why it can be useful to use functions as values, let?s consider a timer f u n ction whose aim is to perform some action every second. H o w do we pass it the action to perform? Quite logically, as a function. This very simple kind of function passing should be familiar to many programmers: it is often u s e d in user-interface code, to register call-back f u n ct i on s which get called when some event occurs.

为了解释为什么吧函数当作值进行操作是十分有用的,我们来考虑一个计时器函数。这个函数的目的是每隔一段时间就执行某些操作。那么如何吧我们要做的操作传入计时器呢?于是我们想吧他当作一个函数。这种目前的函数对于经常进行用户界面编程的程序员来说是最熟悉的:注册一个回调函数以便在事件发生后得到通知。

In the following program, the timer function is called oncePerSecond, and it g ets a call-back function as argument. The type of this function is written () => Unit and is the type of all functions which take no arguments and return nothing (th e type Unit is similar to void in C/C++). The main function of this program simply calls this timer function with a call-back which prints a sentence on the ter mi n al. In other words, this program endlessly prints the sentence “time flies like an arr o w” every second.

在下面的程序中,计时器函数被叫做oncePerSceond,它接受一个回调函数作为参数。这种函数的类型被写作() => Unit ,他们不接受任何参数也没有任何返回(Unit关键字类似于C/C++中的void)。程序的主函数调用计时器并传递一个打印某个句子的函数作为回调。换句话说,这个程序永无止境的每秒打印一个“time flies like an arr o w”。

object Timer {

def oncePerSecond(callback: () => Unit) {

while (true) { callback(); Thread sleep 1000 }

}

def timeFlies() {

println("time flies like an arrow...")

}

def main(args: Array[String]) {

oncePerSecond(timeFlies)

}

}

Note that in order to print the string, we used the predefined method println in- stead of using the one from System.out.

注意,我们输出字符串时使用了一个预定义的函数println而不是使用System.out中的那个。

4.2.1 Anonymous functions

匿名函数

While this program is easy to understand, it can be refined a bit. First of all, no- tice that the f u n ct i on timeFlies is only defined in order to be passed later to th e oncePerSecond f u n ction. Having to name that f u n ct i on,which is only used o n ce, might seem unnecessary, and it would in fact be nice to be able to construct th i s f u n ction just as it is passed to oncePerSecond. This is possible in Scala using anon y- mous functions, which are exactly that: functions without a name. The revised v er- sion of our timer program using an anonymous function instead of timeFlies looks like t ha t:

我们可以吧这个程序改的更加易于理解。首先我们发现定义函数timeFlies的唯一目的就是当作传给oncePerSecond的参数。这么看来给这种只用一次的函数命名似乎没有什么太大的必要,事实上我们可以在用到这个函数的时候再定义它。这些可以通过匿名函数在Scala中实现,匿名函数顾名思义就是没有名字的函数。我们在新版的程序中将会使用一个匿名函数来代替原来的timeFlise函数,程序看起来像这样:

object TimerAnonymous {

def oncePerSecond(callback: () => Unit) {

while (true) { callback(); Thread sleep 1000 }

}

def main(args: Array[String]) {

oncePerSecond(() =>

println("time flies like an arrow..."))

}

}

The presence of an anonymous function in this example is revealed by the right a r- row …=>?which separates the f u n ction?s argument list from its body. In this exa mple, the argument list is empty, as witnessed by the empty pair of parenthesis on the left of the arrow. The body of the function is the same as the one of timeFlies abo v e.

本例中的匿名函数使用了一个箭头(=>)吧他的参数列表和代码分开。在这里参数列表是空的,所以我们在右箭头的左边写上了一对空括号。函数体内容与上面的timeFlise是相同的。

5 Classes

As we have seen above, Scala is an object-oriented language, and as such it has a concept of class.Classes in Scala are declared using a syntax which is close to J av a?s syntax. One important difference is that classes in Scala can have parameters. Th i s is illustrated in the following definition of complex n umb er s.

正如我们所见,Scala是一门面向对象的语言,因此它拥有很多关于“类”的描述1。Scala中的类使用和Java类似的语法进行定义。但是一个重要的不同点在于Scala

中的类可以拥有参数,这样就可以得出我们下面关于对复数类(Complex)的定义:

class Complex(real: Double, imaginary: Double) {

def re() = real

def im() = imaginary

}

This complex class takes two arguments, which are the real and imaginary p ar t of the complex. These arguments must be passed when creating an instance of class Complex, as follows: new Complex(1.5, 2.3). The class contains two meth- ods, called re and im, which give access to these two p ar t s.

我们的复数类(Complex)接受两个参数:实部和虚部。这些参数必须在实例化

时进行传递,就像这样:new Complex(1.5, 2.3)。类定义中包括两个叫做re和im的方法,分别接受上面提到的两个参数。

It should be noted that the return type of these two methods is not given explicitly. It will be inferred automatically by the compiler, which looks at the right-hand side of these methods and deduces that both return a value of type Double.

值得注意的是这两个方法的返回类型并没有显式的声明出来。他们会被编译器

自动识别。在本例中他们被识别为Double

The compiler is not always able to infer types like it does here, and there is unfor t u- nately no simple rule to know exactly when it will be, and when not. In practice, th i s is usually not a problem since the compiler complains when it is not able to infer a type which was not given explicitly. As a simple rule, beginner Scala pr og r ammer s should try to omit type declarations which seem to be easy to deduce from the c o n- text, and see if the compiler agrees. After some time, the programmer should get a good feeling about when to omit types, and when to specify them explicitly.

但是编译器并不总是像本例中的那样进行自动识别。不幸的是关于什么时

候识别,什么时候不识别的规则相当冗杂。在实践中这通常不会成为一个

1For the sake of completeness, it should be noted that some object-oriented languages do n ot have the concept of class, but Scala is not one of t hem.

为了不误导读者,我们应该注意到事实上一些面向对象的语言并不包含对类的描述。但是显然Scala

不属于这类语言。

问题,因为当编译器处理不了的时候会发出相当的抱怨。作为一个推荐的原则,Scala的新手们通常可以试着省略类型定义而让编译器通过上下文自己判断。久而久之,新手们就可以感知到什么时候应该省略类型,什么时候不应该。

5.1 Methods without ar guments

无参方法。

A small problem of the methods re and im is that, in order to call them, one has to put an empty pair of parenthesis after their name, as the following example sh o ws:

关于方法re和im还有一个小问题:你必须在名字后面加上一对括号来调用它们。请看下面的例子:

object ComplexNumbers {

def main(args: Array[String]) {

val c = new Complex(1.2, 3.4)

println("imaginary part: " + c.im())

}

}

It would be nicer to be able to access the real and imaginary parts like if they w ere fields, without putting the empty pair of parenthesis. This is perfectly doable in Scala, simply by defining them as methods without arguments. Such methods differ from methods with zero arguments in that they don?t have parenthesis after t heir name, neither in their definition nor in their use. Our Complex class can be r ewr itt en as f ollo ws:

你可能觉得吧这些函数当作变量使用,而不是当作函数进行调用,可能会更加令人感到舒服。事实上我们可以通过定义无参函数在Scala做到这点。这类函数与其他的具有0个参数的函数的不同点在于他们定义时不需要在名字后面加括弧,所以在使用时也不用加(但是无疑的,他们是函数),因此,我们的Complex类可以重新写成下面的样子;

class Complex(real: Double, imaginary: Double) {

def re = real

def im = imaginary

}

5.2 Inheritance and o verriding

继承和覆盖

All classes in Scala inherit from a super-class. When no super-class is specified, as in the Complex example of previous section, scala.AnyRef is implicitly used. Scala中的所有类都继承一个父类,当没有显示声明父类时(就像上面定义的Complex一样),它们的父类隐形指定为scala.AnyRef。

It is possible to override methods inherited from a super-class in Scala. It is ho w- ever mandatory to explicitly specify that a method overrides another one using th e override modifier, in order to avoid accidental overriding. As an example, ou r Complex class can be augmented with a redefinition of the toString method inher- ited from Object.

在子类中覆盖父类的成员是可能的。但是你需要通过override修饰符显示指定成员的覆盖。这样的规则可以避免意外覆盖的情况发生。作为演示,我们在Complex 的定义中覆盖了Object的toString方法。

class Complex(real: Double, imaginary: Double) {

def re = real

def im = imaginary

override def toString() =

"" + re + (if (im < 0) "" else "+") + im + "i"

}

6 Case classes and pattern matc hing

条件类和模式匹配

A kind of data structure that often appears in programs is the tree. For example, i n- terpreters and compilers usually represent programs internally as trees; XML doc- uments are trees; and several kinds of containers are based on trees, like re d-blac k t rees.

树是在程序中常用的一个数据结构。例如编译器和解析器常常吧程序表示为树;XML文档结构也是树状的;还有一些集合是基于树的,例如红黑树。

We will now examine how such trees are represented and manipulated in S cal a through a small calculator program. The aim of this program is to manipulate v er y simple arithmetic expressions composed of sums, integer constants and v ar i ab l es. Two examples of such expressions are 1 + 2 and (x + x)+ (7 + y).

接下来我们将通过一个计算器程序来研究树在Scala中是如何表示和操纵的。这个程序的目标是处理一些由整数常量、变量和加号组成的简单的算数表达式,例如1 + 2 and (x + x)+ (7 + y)。

We first have to decide on a representation for such expressions. The most n atu r al one is the tree, where nodes are operations (here, the addition) and leaves are v alue s (here constants or v ar i a bl es).

我们首先要决定如何表示这些表达式。最自然的方法就是树了,树的节点表示操作符(在这里只有加法),而树的叶节点表示值(这里表示常数和变量)。In Java, such a tree would be represented using an abstract super-class for the t rees, and one concrete sub-class per node or leaf. In a functional programming l an guage, one would use an algebraic data-type for the same purpose. Scala provides the c o n- cept of case classes which is somewhat in between the two. Here is how they can b e used to define the type of the trees for our exam ple:

在Java中,这样的树可以表示为一个超类的树的集合,节点由不同子类的实例表示。而在函数式语言中,我们可以使用代数类型(algebraic data-type)来达到同样的目的。Scala提供了一种介于两者之间的叫做条件类(Case Classes)的东西。

abstract class Tree

case class Sum(l: Tree, r: Tree) extends Tree

case class Var(n: String) extends Tree

case class Const(v: Int) extends Tree

The fact that classes Sum, Var and Const are declared as case classes means that th ey differ from standard classes in several respect s:

我们实际上定义了三个条件类Sum ,Var 和Const 。这些类和普通类有若干不同:

? the new keyword is not mandatory to create instances of these classes (i.e. one can write Const(5) instead of new Const(5)),

实例化时可以省略new关键字(例如你可以使用Const(5)而不必使用new Const(5) )

? getter functions are automatically defined for the constructor parameters (i.e.

it is possible to get the value of the v constructor parameter of some inst an ce

c of class Const just by writing c.v),

参数的getter函数自动定义(例如你可以通过c.v来访问类Const的实例c在实例化时获取的参数v)

? default definitions for methods equals and hashCode are provided, which w or k on the structure of the instances and not on their i den tit y,

拥有默认的预定义equals和hashCode实现,这些实现可以按照值区别类实例是否相等,而不是通过用。

? a default definition for method toString is provided, and prints the value in a “source form”(e.g. the tree for expression x+1 prints as Sum(Var(x),Const(1))), 拥有默认的toString实现。这些实现返回值的代码实现(例如表达式x+1可以被表达成Sum(Var(x),Const(1)))

? instances of these classes can be decomposed through pattern matching as we will see belo w.

条件类的实例可以通过模式匹配进行分析,我们接下来就要讲这个特性。Now that we have defined the data-type to represent our arithmetic expressions, w e can start defining operations to manipulate them. We will start with a f u n ction t o evaluate an expression in some environment. The aim of the environment is to giv e values to variables. For example, the expression x + 1 evaluated in an en v ir onment which associates the value 5 to variable x,written {x→5}, gives 6 as re s u lt.

现在我们已经定义了表示我们算数表达式的数据类型,于是我们可以开始给他们定义对应的操作。我们将会首先编写一个在上下文中下计算表达式的函数。这里的上下文指的是变量与值的绑定关系。例如表达式x+1在x=5上下文中应该得出结果6。

We therefore have to find a way to represent environments. We could of course use some associative data-structure like a hash table, but we can also directly use f u n ctions! An environment is really nothing more than a function which associate s a value to a (variable) name. The environment {x→ 5} given above can simply b e written as follows in Scala:

这样一来我们需要找到一个表示这种绑定关系的方法。当然我们可以使用某种类似hash-table的数据结构,不过我们也可以直接使用函数!一个上下文无非就是一个吧名称映射到值的函数。例如上面给出的{x→5}的这个映射我们就可以在Scala中表示为:

{ case "x" => 5 }

This notation defines a f u n ct i on which, when given the string "x" as argument, re- turns the integer 5, and fails with an exception oth er wi se.

这个定义了一个函数:当参数等于字符串"x" 时返回整数5,否则抛出异常。Before writing the evaluation f u n ct i on, let us give a name to the type of the e n vi ro n- ments. We could of course always use the type String => Int for envi r onmen ts, but it simplifies the program if we introduce a name for this type, and makes f u t u re changes easier. This is accomplished in Scala with the following not ation:

在编写求值函数之前我们,我们需要给我们的上下文起个名字,以便在后面的代码里面引用。理所应当的我们使用了类型String=>Int,但是如果我们给这个类型

起个名字,将会让程序更加简单易读,而且更加容易维护。在scala中,这件事情

可以通过以下代码完成:

type Environment = String => Int

From then on, the type Environment can be used as an alias of the type of funct io n s from String to Int.

从现在开始,类型Environment就当作String到Int的函数类型名来使用了。

We can now give the definition of the evaluation function. Conceptually, it is v er y simple: the value of a sum of two expressions is simply the sum of the value of th ese expressions; the value of a variable is obtained directly from the en v ir o n ment;and the value of a constant is the constant itself. Expressing this in Scala is not mor e diffi cu lt:

现在我们可以开始定义求值函数了。从概念上来说,这是很简单的一个过程:两

个表达式之和等于两个表达式分别求值后再求和;变量的值可以从上下文中提取;常量的值就是他本身。在Scala中表达这个没有什么难度:

def eval(t: Tree, env: Environment): Int = t match {

case Sum(l, r) => eval(l, env) + eval(r, env)

case Var(n) => env(n)

case Const(v) => v

}

This evaluation f u n ct i on works by performing pattern matching on the tree t. I nt u- itively, the meaning of the above definition should be c l ear:

求值函数通过对树t进行模式匹配来完成工作。直观的来看,上述代码的思路是十分清晰的:

1.it first checks if the tree t is a Sum, and if it is, it binds the left sub-tree to a

new variable called l and the right sub-tree to a variable called r, and then

pr o- ceeds with the evaluation of the expression following the arrow; this

expr es- sion can (and does) make use of the variables bound by the pattern

a ppear i n g on the left of the arrow, i.e. l and r,

第一个模式检查传入的树的根节点是否是一个Sum,如果是,它将会吧树的左

边子树赋值给l,右边的子树赋值给r,然后按照箭头后面的代码进行处理;这里

的代码可以(并且的确)使用了在左边匹配时所绑定的变量,比如这里的l和

r。

2.if the first check does not succeed, that is if the tree is not a Sum, it goes on

and checks if t is a Var; if it is, it binds the name contained in the Var

node to a variable n and proceeds with the right-hand exp ression,

如果第一个检查没有成功,表明传入的树不是Sum,程序继续检查他是不是一个

Var;如果是,则吧变量名赋给n然后继续右边的操作。

3.if the second check also fails, that is if t is neither a Sum nor a Var, it

ch ecks if it is a Const, and if it is, it binds the value contained in the Const

node to a variable v and proceeds with the right-hand side,

如果第二个检查也失败了,表示t既不是Sum也不是Var,程序检查他是不是

Const。如果是着赋值变量并且继续。

4.finally, if all checks fail, an exception is raised to signal the failure of the

pat- tern matching expression; this could happen here only if more sub-

classes of Tree were dec l ar ed.

最后,如果所有检查都失败了。就抛出一个异常表示模式匹配失败。这只有在

Tree的其他之类被定义时才可能发生。

We see that the basic idea of pattern matching is to attempt to match a value to a series of patterns, and as soon as a pattern matches, extract and name various p arts of the value, to finally evaluate some code which typically makes use of these nam e d pa r ts.

我们可以看出模式匹配的基本思想就是试图对一个值进行多种模式的匹配,并且在匹配的同时将匹配值拆分成若干子项,最后对匹配值与其子项执行某些代码。

A seasoned object-oriented programmer might wonder why we did not define eval as a method of class Tree and its subclasses. We could have done it actually, s i n ce Scala allows method definitions in case classes just like in normal classes. D eciding whether to use pattern matching or methods is therefore a matter of taste, but it also has important implications on exten sibi lity:

一个熟练的面向对象的程序员可能想知道为什么我们不吧eval定义为Tree或者其之类的成员函数。我们事实上可以这么做。因为Scala允许条件类象普通类那样定义成员。决定是否使用模式匹配或者成员函数取决于程序员的喜好,不过这个取舍还和可扩展性有重要联系:

? when using methods, it is easy to add a new kind of node as this can be don e

just by defining the sub-class of Tree for it; on the other hand, adding a n e w

operation to manipulate the tree is tedious, as it requires modifications to all

sub-classes of Tree,

当你使用成员函数时,你可以通过继承Tree从而很容易的添加新的节点类型,但是另外一方面,添加新的操作也是很繁杂的工作,因为你不得不修改Tree的所有子类。

? when using pattern matching, the situation is reversed: adding a new kind of

node requires the modification of all functions which do pattern matching on

the tree, to take the new node into acc o unt; on the other hand, adding a n ew

operation is easy, by just defining it as an independent function.

当你使用模式匹配是,形势正好逆转过来,添加新的节点类型要求你修改所有

的对树使用模式匹配的函数,但是另一方面,添加一个新的操作只需要再添加

一个模式匹配函数就可以了。

To explore pattern matching f u r th er, let us define another operation on ar it hmet i c expressions: symbolic derivation. The reader might remember the following rule s regarding this oper ation:

下面我们来更详细的了解模式匹配,让我们再给表达式定义一个操作:对

符号求导数。读者们也许想先记住下面关于此操作的若干规则:

1.the derivative of a sum is the sum of the der iv ativ es,

和的导数等于导数的和,

2.the derivative of some variable v is one if v is the variable relative to which t he

derivation takes place, and zero o t her w ise,

如果符号等以求导的符号,则导数为1,否则为0.

3.the derivative of a constant is z ero.

参数的导数永远为0。

These rules can be translated almost literally into Scala code, to obtain the foll o wing defin i t ion:

上述规则可以直接翻译成Scala代码:

def derive(t: Tree, v: String): Tree = t match {

case Sum(l, r) => Sum(derive(l, v), derive(r, v))

case Var(n) if (v == n) => Const(1)

case _ => Const(0)

}

This f u n ct i on introduces two new concepts related to pattern matching. First of a l l, the case expression for variables has a guard, an expression following the if key- word. This guard prevents pattern matching from succeeding unless its e x pr ess i on is true. Here it is used to make sure that we return the constant 1 only if the name of the variable being derived is the same as the derivation variable v. The second n ew f e a tur e of pattern matching used here is the wild-card, written _, which is a pa tt er n matching any value, without giving it a n ame.

这个函数使用了两个关于模式匹配的功能,首先case语句可以拥有一个guard子句:一个if条件表达式。除非guard的条件成立,否则该模式不会成功匹配。其次是通配符:_ 。这个模式表示和所有值匹配而不对任何变量赋值。

We did not explore the whole power of pattern matching yet, but we will stop her e in order to keep this document short. We still want to see how the two f u n ctions above perform on a real example. For that purpose, let?s write a simple main f u n c- tion which performs several operations on the expression (x + x) + (7 + y): it first computes its value in the environment {x→ 5, y → 7}, then computes its der iv ativ e relative to x and then y.

事实上我们还远没有触及模式匹配的全部精髓。但是我们限于篇幅原因不得不再此停笔了。下面我们看看这个两个函数是如何在一个实例上运行的。为了达到这个目前我们写了一个简单的main函数来对表达式(x + x) + (7 + y)进行若干操作:首先计算当{x→ 5, y → 7}时表达式的值,然后分别对x和y求导。

def main(args: Array[String]) {

val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))

val env: Environment = { case "x" => 5 case "y" => 7 }

println("Expression: " + exp)

println("Evaluation with x=5, y=7: " + eval(exp, env))

println("Derivative relative to x:\n " + derive(exp, "x"))

println("Derivative relative to y:\n " + derive(exp, "y")) }

Executing this program, we get the expected outpu t:

执行程序,我们能得到以下输出:

Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y)))

Evaluation with x=5, y=7: 24

Derivative relative to x:

Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0)))

Derivative relative to y:

Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))

By examining the output, we see that the result of the derivative should be sim- plified before being presented to the user. Defining a basic simplification f u n ct i on using pattern matching is an interesting (but surprisingly tricky) problem, left as an exercise for the r eader.

通过研究程序输出,我们能看到求导的输出可以在被打印之前简化,使用模式匹配定义一个简化函数是挺有意思的(不过也需要一定的技巧)工作。读者可以尝试自己完成这个函数。

7 T rait

Apart from inheriting code from a super-class, a Scala class can also import c ode from one or several tr ait s.

除了从父类集成代码外,Scala中的类还允许从一个或者多个tr ait s中导入代码。Maybe the easiest way for a Java programmer to understand what traitss are is t o view them as interfaces which can also contain code. In Scala, when a class inher its from a trait, it implements that tr aits?s interface, and inherits all the code contain e d in the t rait.

对于Java程序员来说理解traits的最好方法就是把他们当作可以包含代码的接口(interface)。在Scala中,当一个类继承一个trait时,它就实现了这个trait的接口,同时还从这个trait中继承了所有的代码。

To see the usefulness of traits, let?s look at a classical example: ordered objects.

I t is o f ten useful to be able to compare objects of a given class among t hemselv es, for example to sort them. In Java, objects which are comparable implement th e Comparable interface. In Scala, we can do a bit better than in Java by defining ou r equivalent of Comparable as a trait, which we will call Ord.

让我们通过一个典型的实例来看看这种trait机制是如何发挥作用的:排序对象。能够比较若干给定类型的对象在实际应用中是很有用的,比如在进行排序时。在Java语言中可以比较的对象是通过实现Comparable接口完成的。在Scala中我们可以通过吧Comparable定义为trait来做的比Java好一些。我们吧这个trait叫做Ord。

When comparing objects, six different predicates can be useful: smaller, sma l ler or equal, equal, not equal, greater or equal, and greater. However, defining all of them is fastidious, especially since four out of these six can be expressed using th e remaining two. That is, given the equal and smaller predicates (for example), on e can express the other ones. In Scala, all these observations can be nicely c aptu re d by the following trait dec l ar at i on:

在比较对象时,一下六种关系通常使用率最高:小于、小于等于、等于、不等于、大于等于、大于。但是把他们都定义一次无疑是很没用而且繁琐的。尤其是六种关系中的四种其实是可以通过其他两种关系导出的。例如给定等于和小于的定义后就可以推导出其他的定义。于是在Scala中,这些推导可以通过下面这个trait实现:

trait Ord {

def < (that: Any): Boolean

def <=(that: Any): Boolean = (this < that) || (this == that)

def > (that: Any): Boolean = !(this <= that)

def >=(that: Any): Boolean = !(this < that)

}

This definition both creates a new type called Ord, which plays the same role a s J av a?s Comparable interface, and default implementations of three predicates in

t erms of a fourth, abstract one. The predicates for equality and inequality do not app ear here since they are by default present in all o b j ect s.

这个定义在建立了一个叫做与Java中的 Comparable 等效的叫做 Ord的类型的同时还实现了使用抽象的一种关系推导其他三种的接口。比较相等性的方法没有出现是由于他已经默认存在于所有对象中了。

The type Any which is used above is the type which is a super-type of all other types in Scala. It can be seen as a more general version of J a v a?s Object type, since it is

also a super-type of basic types like Int, Float, etc.

上面使用的叫做Any的类型表示了Scala中所有类的共同超类。事实上它就等于Java语言中的Object。

To make objects of a class comparable, it is therefore sufficient to define the p redi- cates which test equality and inferiority, and mix in the Ord class above. As an exam- ple, let?s define a Date class representing dates in the Gregorian calendar. Such dates are composed of a day, a month and a year, which we will all represent as in tegers. We therefore start the definition of the Date class as foll o w s:

要使的一个类可以被比较,就需要可以比较他们是否相等或者大小关系,而这些都混合在上面的类Ord中了。现在我们来写一个Date类来表示格利高里历中的日期。这个日期由年、月、日三个部分组成,每个部分都可以用一个整数表示。所有我们就得出了下面这个定义:

class Date(y: Int, m: Int, d: Int) extends Ord {

def year = y

def month = m

def day = d

override def toString(): String = year + "-" + month + "-" + day

The important part here is the extends Ord declaration which follows the class nam e and parameters. It declares that the Date class inherits from the Ord t rait.

注意在类名后出现的extends Ord。这表示了这个类继承了Ord这个trait。

Then, we redefine the equals method, inherited from Object, so that it correct ly compares dates by comparing their individual fields. The default implement at i on of equals is not usable, because as in Java it compares objects physically. We arr iv e at the following definit i on:

然后我们重新定义了equals这个从Object继承来的方法,好让他能够正确的比较我们日期中的每个部分。原来的equals函数的行为与Java中的一样,是按照对象的指针进行比较的。我们可以得出下面的代码。

override def equals(that: Any): Boolean =

that.isInstanceOf[Date] && {

val o = that.asInstanceOf[Date]

o.day == day && o.month == month && o.year == year

}

This method makes use of the predefined methods isInstanceOf and asInstanceOf. The first one, isInstanceOf, corresponds to J av a?s instanceof operator, and r etur ns true if and only if the object on which it is applied is an instance of the given type. The second one, asInstanceOf, corresponds to J av a?s cast op er at or:if the object is an instance of the given type, it is viewed as such, otherwise a ClassCastException is th ro wn.

这个函数使用了预定义函数isInstanceOf 和asInstanceOf 。第一个isInstanceOf 类似Java中的instanceof :当且仅当对象是给定类型的实例时才返回true。第二个 asInstanceOf 对应Java中的类型转换操作:当对象是给定类型的子类时转换,

否则抛出ClassCastException。

Finally, the last method to define is the predicate which tests for inferiority, as fol- lows. It makes use of another predefined method, error, which throws an ex c eption with the given error message.

最后我们还需要定义测试小于关系的函数,如下面所示。这个函数使用了预定义

的函数error ,它可以使用给定字符串抛出一个异常。

def <(that: Any): Boolean = {

if (!that.isInstanceOf[Date])

error("cannot compare " + that + " and a Date")

val o = that.asInstanceOf[Date]

(year < o.year) ||

(year == o.year && (month < o.month ||

(month == o.month && day < o.day)))

}

This completes the definition of the Date class. Instances of this class can be seen either as dates or as comparable objects. Moreover, they all define the six compar i- son predicates mentioned above: equals and < because they appear directly in th e definition of the Date class, and the others because they are inherited from the Ord t rait.

以上就是Data的完整定义了。这个类的实例既可以作为日期显示,也可以进行比较。而且他们都定义了6种比较操作:其中两种: equals和< 是我们直接定义的,而其他的是从Ord中继承的。

Traits are useful in other situations than the one shown here, of course, but dis- cussing their applications in length is outside the scope of this docum e n t.

Traits 的应用远不止于此,不过更加深入的讨论不再本文的讨论范围内。

相关主题
相关文档
最新文档