《Scala Tutorial 中文版》 草稿

闲话:好几天之前就开始翻译这个东西了,不过由于这几天感冒一直没怎么动。另外由于我也才刚刚接触Scala,错误再所难免。明天上路回家,到了家里再把review一下吧。

正式版在这里:http://blog.icybear.net/2009/07/a-scala-tutorial-for-java-programmers.html

=-=-=-=-=正文分割线=-=-=-=-=


ScalaTutorial

A Scala Tutorial

for Java  programmers

Version 1.3

March 15, 2009

Michel Schinz, Philipp

Haller

Translated to Simplified Chinese by Bearice

PRO G R A M M I N G ME T H O D S L A B O R ATO RY

EPFL SW I T Z E R L A N D

2

1    Introduction 简介

This document gives a quick  introduction to the  Scala  language and  compiler.  It is intended for people who  already have  some programming experience and  want an overview of what  they  can  do with Scala.  A basic  knowledge of object-oriented programming, especially in Java, is assumed.

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

2    A first example 第一个例子

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

作为学习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 consists of one  method called main which takes  the  command line  arguments, an array  of strings, as parameter; the  body  of this  method consists of a single  call to the  pre- 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 to declare a return type.

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

What  is less familiar to Java programmers is the  object declaration containing the main method. Such  a declaration introduces what  is commonly known as a single- 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 used.

对于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. Rather than defining static members, the  Scala  programmer declares these members in singleton objects.

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

2.1   Compiling the example 编译实例

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  produces 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  shell prompt and  should not be typed):

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

2.2    Running  the example                                                                                                                           3

> scalac HelloWorld.scala

This will generate a few class files in the current directory. One of them will be called HelloWorld.class, and  contains a class  which can  be directly executed using the scala command, as the following section shows.

这将会在当前目录生成一系列.class文件。其中的一个名为HelloWorld.class 的文件中定义了一个可以直接使用scala命令执行的类。下文中你可以看到这个例子。

2.2   Running  the example 运行实例

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 same options. The above example can be executed using the following command, which produces the expected output:

一旦完成编译,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  java.lang package are imported by default, while  others need to be imported explicitly.

Scala的一个强项在于可以很简单的于已有的Java代码交互,所有java.lang中的类都已经被自动导入了,而其他的类需要显式声明导入。

Let’s look at an example that demonstrates this.  We want to obtain and  format the current date according to the conventions used in a specific country, say France1 .

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

Java’s class  libraries define powerful utility  classes, such as Date and  DateFormat. Since Scala interoperates seemlessly with Java, there is no need to implement equiv- alent classes in the Scala class library–we can  simply import the  classes of the  cor- responding Java packages:

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)

}

}

1 Other regions such as the french speaking part of Switzerland use the same conventions.

其他法语国家(例如瑞士法语区)使用同样的格式。

4

Scala’s import statement looks very similar to Java’s equivalent, however, it is more powerful.  Multiple classes can  be imported from  the  same package by enclosing them in curly braces as on the first line.  Another difference is that when importing all the names of a package or class, one uses the underscore character (_) instead of the  asterisk (*).  That’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 visible.

第三行的import语句导入了DataFormat类中的所有成员,这使得静态方法getDateInstance和静态变量LONG可以被直接引用。

Inside the  main method we first  create an  instance of Java’s Date class  which by default contains the  current date.  Next,  we define a date format using the  static getDateInstance method that we imported previously. Finally, we print the current date formatted according to the localized DateFormat instance. This last line shows an interesting property of Scala’s syntax.  Methods taking one argument can be used with an infix syntax. That is, the expression

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

df format now

is just another, slightly less verbose way of writing the expression

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

df.format(now)

This might seem like a minor syntactic detail, but  it has  important consequences, 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 Scala.

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

4    Everything is an object 万物皆对象

Scala  is a pure object-oriented language in the  sense that everything is an  object, 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 functions as values.

Scala作为一个纯面向对象的语言,于是在Scala中万物皆对象,包括数字和函数。在这方面,Scala于Java存在很大不同:Java区分原生类型(比如boolean和int)和引用类型,并且不能吧函数当初变量操纵。

4.1   Numbers are objects 数字和对象

Since  numbers are objects, they  also have  methods. And in fact, an arithmetic ex- pression like the following:

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

1 + 2 * 3 / x

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

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

4.2    Functions are objects                                                                                                                              5

(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 fol- 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, making 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, functions are also objects in Scala. It is therefore possible to pass  functions as arguments, to store them in variables, and  to return them from  other functions.  This ability  to manipulate functions as values  is one of the cornerstone of a very interesting programming paradigm called functional programming.

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

As a very simple example of why it can  be useful to use  functions as values, let’s consider a timer function whose aim is to perform some action every second. How 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 used in user-interface code, to register call-back functions which get called  when some event occurs.

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

In the  following program, the  timer function is called  oncePerSecond, and  it gets 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 (the 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  terminal. In other words, this program endlessly prints the sentence “time  flies like an arrow” every second.

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

object Timer {

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

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

}

def timeFlies() {

println(“time flies like an arrow…”)

}

6

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  function timeFlies is only  defined in order to be passed later  to the oncePerSecond function.  Having  to name that function, which is only  used once, might seem unnecessary, and  it would in fact be nice  to be able  to construct this function just as it is passed to oncePerSecond. This is possible in Scala using  anony- mous functions, which are exactly that: functions without a name. The revised ver- sion of our timer program using an anonymous function instead of timeFlies looks like that:

我们可以吧这个程序改的更加易于理解。首先我们发现定义函数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 ar- row ‘=>’ which separates the function’s argument list from its body.  In this example, 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 above.

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

5    Classes

As we have  seen  above, Scala is an  object-oriented language, and  as such it has  a concept of class.2 Classes in Scala are declared using a syntax which is close to Java’s syntax. One important difference is that classes in Scala can have parameters. This is illustrated in the following definition of complex numbers.

正如我们所见,Scala是一门面向对象的语言,因此它拥有很多关于“类”的描述。Scala中的类使用和Java类似的语法进行定义。但是一个重要的不同点在于Scala中的类可以拥有参数,这样就可以得出我们下面关于对复数类(Complex)的定义:

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

2 For the  sake  of completeness, it should be noted that some object-oriented languages do not have  the concept of class, but Scala is not  one of them.

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

5.1    Methods without arguments                                                                                                                  7

def re() = real

def im() = imaginary

}

This  complex class  takes  two  arguments, which are  the  real  and  imaginary part 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 parts.

我们的复数类(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 unfortu- nately no simple rule to know exactly when it will be, and when not.  In practice, this 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 programmers should try to omit  type declarations which seem to be easy to deduce from the con- 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.

但是编译器并不总是像本例中的那样进行自动识别。不幸的是关于什么时候识别,什么时候不识别的规则相当冗杂。在实践中这通常不会成为一个问题,因为当编译器处理不了的时候会发出相当的抱怨。作为一个推荐的原则,Scala的新手们通常可以试着省略类型定义而让编译器通过上下文自己判断。久而久之,新手们就可以感知到什么时候应该省略类型,什么时候不应该。

5.1   Methods without  arguments 无参方法。

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 shows:

关于方法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  were 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  their name, neither in their definition nor in their use.  Our Complex class can be rewritten as follows:

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

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

def re = real

def im = imaginary

}

8

5.2   Inheritance and overriding 继承和覆盖

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 how- ever mandatory to explicitly specify  that a method overrides another one  using the override modifier, in  order to  avoid  accidental overriding.  As an  example, our 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  matching 条件类和模式匹配

A kind of data structure that often appears in programs is the tree.  For example, in- terpreters and  compilers usually represent programs internally as trees;  XML doc- uments are trees;  and  several kinds  of containers are based on trees, like red-black trees.

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

We will now  examine how  such trees  are  represented and  manipulated in  Scala through a small  calculator program. The aim of this program is to manipulate very simple arithmetic expressions composed of sums, integer constants and  variables. 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 natural one is the tree, where nodes are operations (here, the addition) and leaves are values (here constants or variables).

我们首先要决定如何表示这些表达式。最自然的方法就是树了,树的节点表示操作符(在这里只有加法),而树的叶节点表示值(这里表示常数和变量)。

In Java, such a tree would be represented using  an abstract super-class for the trees, and one concrete sub-class per node or leaf. In a functional programming language, one would use an algebraic data-type for the same purpose. Scala provides the con- cept  of case classes which is somewhat in between the two. Here is how they can be used to define the type of the trees  for our example:

在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 they differ from standard classes in several respects:

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

6  Case classes and pattern matching                                                                                                            9

•  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 instance 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 work on the structure of the instances and  not on their  identity,

拥有默认的预定义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 below.

条件类的实例可以通过模式匹配进行分析,我们接下来就要讲这个特性。

Now that we have defined the data-type to represent our arithmetic expressions, we can  start defining operations to manipulate them. We will start with  a function to evaluate an expression in some environment. The aim of the environment is to give values  to variables. For example, the expression x + 1 evaluated in an environment which associates the value 5 to variable x , written {x → 5}, gives 6 as result.

现在我们已经定义了表示我们算数表达式的数据类型,于是我们可以开始给他们定义对应的操作。我们将会首先编写一个在上下文中下计算表达式的函数。这里的上下文指的是变量与值的绑定关系。例如表达式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 functions! An environment is really nothing more than a function which associates a value  to a (variable) name.  The environment {x → 5} given  above can  simply be written as follows in Scala:

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

{ case “x” => 5 }

This notation defines a function which, when given the string “x” as argument, re- turns the integer 5, and  fails with an exception otherwise.

这个定义了一个函数:当参数等于字符串”x” 时返回整数5,否则抛出异常。

Before writing the evaluation function, let us give a name to the type of the environ- ments. We could of course always  use  the  type  String => Int for environments, but it simplifies the program if we introduce a name for this type, and  makes future changes easier. This is accomplished in Scala with the following notation:

在编写求值函数之前我们,我们需要给我们的上下文起个名字,以便在后面的代码里面引用。理所应当的我们使用了类型String=>Int,但是如果我们给这个类型起个名字,将会让程序更加简单易读,而且更加容易维护。在scala中,这件事情可以通过以下代码完成:

type Environment = String => Int

From  then on, the type Environment can be used as an alias of the type of functions from String to Int.

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

We can  now  give the  definition of the  evaluation function. Conceptually, it is very simple: the value of a sum of two expressions is simply the sum of the value of these expressions; the value  of a variable is obtained directly from  the environment; and the  value  of a constant is the  constant itself.  Expressing this  in Scala  is not  more difficult:

现在我们可以开始定义求值函数了。从概念上来说,这是很简单的一个过程:两个表达式之和等于两个表达式分别求值后再求和;变量的值可以从上下文中提取;常量的值就是他本身。在Scala中表达这个没有什么难度:

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

10

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

case Var(n)   => env(n)

case Const(v) => v

}

This evaluation function works by performing pattern matching on the tree t. Intu- itively, the meaning of the above definition should be clear:

求值函数通过对树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 pro- ceeds with the  evaluation of the  expression following the  arrow;  this  expres- sion can (and does) make use of the variables bound by the pattern appearing on the left of the arrow, i.e. l and  r,

第一个模式检查传入的树的根节点是否是一个Sum,如果是,它将会吧树的左边子树赋值给l,右边的子树赋值给r,然后按照箭头后面的代码进行处理;这里的代码可以(并且的确)使用了在左边匹配时所绑定的变量,比如这里的l和r。

  1. 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 expression,

如果第一个检查没有成功,表明传入的树不是Sum,程序继续检查他是不是一个Var;如果是,则吧变量名赋给n然后继续右边的操作。

  1. if the  second check  also fails, that is if t is neither a Sum nor  a Var, it checks 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。如果是着赋值变量并且继续。

  1. 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 declared.

最后,如果所有检查都失败了。就抛出一个异常表示模式匹配失败。这只有在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 parts of the value, to finally evaluate some code which typically makes use of these named parts.

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

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, since Scala allows method definitions in case classes just like in normal classes. Deciding whether to use  pattern matching or methods is therefore a matter of taste, but  it also has important implications on extensibility:

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

•  when using methods, it is easy to add  a new kind of node as this can be done just by defining the  sub-class of Tree for it; on the  other hand, adding a new 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  account; on the other hand, adding a new operation is easy, by just defining it as an independent function.

当你使用模式匹配是,形势正好逆转过来,添加新的节点类型要求你修改所有的对树使用模式匹配的函数,但是另一方面,添加一个新的操作只需要再添加一个模式匹配函数就可以了。

6  Case classes and pattern matching                                                                                                         11

To explore pattern matching further, let us define another operation on arithmetic expressions: symbolic derivation. The reader might remember the  following rules regarding this operation:

下面我们来更详细的了解模式匹配,让我们再给表达式定义一个操作:对符号求导数。读者们也许想先记住下面关于此操作的若干规则:

  1. the derivative of a sum  is the sum  of the derivatives,

和的导数等于导数的和,

  1. the derivative of some variable v is one if v is the variable relative to which the derivation takes place, and  zero otherwise,

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

  1. the derivative of a constant is zero.

参数的导数永远为0。

These rules can be translated almost literally into Scala code, to obtain the following definition:

上述规则可以直接翻译成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 function introduces two new concepts related to pattern matching. First of all, the  case expression for variables has  a guard,  an  expression following the  if key- word.  This guard prevents pattern matching from succeeding unless its expression 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 new feature of pattern matching used here  is the wild-card, written _, which is a pattern matching any value, without giving it a name.

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

We did not  explore the  whole  power of pattern matching yet, but  we will stop  here in order to keep  this  document short. We still want to see how  the  two functions above perform on a real example. For that purpose, let’s write  a simple main func- 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 derivative 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 output:

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

Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) Evaluation with x=5, y=7: 24

12

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 function using  pattern matching is an interesting (but surprisingly tricky) problem, left as an exercise for the reader.

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

7    Traits

Apart  from  inheriting code  from  a super-class, a Scala  class  can  also  import code from one or several traits.

除了从父类集成代码外,Scala中的类还允许从一个或者多个traits中导入代码。

Maybe  the  easiest way for a Java programmer to understand what  traitss are  is to view them as interfaces which can also contain code. In Scala, when a class inherits from a trait,  it implements that traits’s interface, and  inherits all the code  contained in the trait.

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

To see the  usefulness of traits, let’s look at a classical example: ordered objects. It is often useful to be  able  to compare objects of a given  class  among themselves, for example to sort  them.  In Java, objects which are  comparable implement the Comparable interface. In Scala, we can  do a bit better than in Java by defining our 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, smaller 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 the remaining two.  That  is, given  the  equal and  smaller predicates (for example), one can  express the  other ones. In Scala, all these observations can  be nicely  captured by the following trait  declaration:

在比较对象时,一下六种关系通常使用率最高:小于、小于等于、等于、不等于、大于等于、大于。但是把他们都定义一次无疑是很没用而且繁琐的。尤其是六种关系中的四种其实是可以通过其他两种关系导出的。例如给定等于和小于的定义后就可以推导出其他的定义。于是在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  as Java’s Comparable interface, and default implementations of three predicates in terms of a fourth, abstract one.  The predicates for equality and  inequality do not  appear here  since  they are by default present in all objects.

这个定义在建立了一个叫做与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 Java’s Object type,  since  it is

7  Traits                                                                                                                                                                13

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 predi- 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 integers. We therefore start the definition of the Date class as follows:

要使的一个类可以被比较,就需要可以比较他们是否相等或者大小关系,而这些都混合在上面的类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 name and  parameters. It declares that the Date class inherits from the Ord trait.

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

Then, we redefine the  equals method, inherited from  Object, so that it correctly compares dates by comparing their  individual fields.  The default implementation of equals is not usable, because as in Java it compares objects physically. We arrive at the following definition:

然后我们重新定义了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 Java’s instanceof operator, and returns 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 Java’s cast  operator: if the  object is an instance of the given type, it is viewed  as such, otherwise a ClassCastException is thrown.

这个函数使用了预定义函数 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 exception 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)))

14

}

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 compari- son predicates mentioned above: equals and  < because they appear directly in the definition of the Date class, and  the others because they are inherited from  the Ord trait.

以上就是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 document.

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

8    Genericity 泛型

The last characteristic of Scala we will explore in this tutorial is genericity. Java pro- grammers should be well aware of the  problems posed by the  lack of genericity in their  language, a shortcoming which is addressed in Java 1.5.

我们在这文章将要学习Scala的最后一个特性是泛型。Java程序员们可能最近才知道这个东西,因为这个特性是在Java1.5中才被加入的。

Genericity is the  ability  to write  code  parametrized by types. For example, a pro- grammer writing a library  for linked lists faces the problem of deciding which type to give to the elements of the list. Since this list is meant to be used in many differ- ent contexts, it is not possible to decide that the type of the elements has to be, say, Int. This would be completely arbitrary and  overly restrictive.

泛型是一种可以让你使用类型参数的设施。例如当一个程序员正在实现一个链表时,将不得不面对诸如如何决定链表中节点保存数据的类型之类的问题。正由于这是一个链表,所以往往会在不同的环境中使用,因此,我们不能草率的决定节点数据类型,比如说Int。这种决定是相当的草率且局限性的。

Java programmers resort to using Object, which is the super-type of all objects. This solution is however far from  being ideal,  since  it doesn’t work for basic  types  (int, long, float, etc.)  and  it implies that a lot of dynamic type casts  have to be inserted by the programmer.

以前Java程序员们通常使用Object,所有类型的超类,来解决问题。但是这种方法远远算不上是理想方案,例如他无法处理基本类型如int、long、float等(1.6中的autobox特性可以解决这个问题——译者注),而且会让使用者不得不使用大量的动态类型转换。

Scala makes it possible to define generic classes (and methods) to solve this  prob- lem. Let us examine this with an example of the simplest container class possible: a reference, which can either be empty or point to an object of some type.

Scala中的泛型机制可以很轻松的解决这些个问题。来看下面这个最简单的容器类:一个引用,可以指向某个对象或者指向空。

class Reference[T] {

private var contents: T = _

def set(value: T) { contents = value }

def get: T = contents

}

The class Reference is parametrized by a type,  called  T, which is the type of its ele- ment. This type is used in the body of the class as the type of the contents variable, the argument of the set method, and  the return type of the get method.

Reference类具有一个叫做T的类型参数来表示他说引用的对象的类型。这个类型在Reference中作为了变量和函数的参数或者返回类型。

The above code  sample introduces variables in Scala, which should not require fur- ther  explanations. It is however interesting to see that the initial value given to that variable is _, which represents a default value.   This default value  is 0 for numeric types,  false for the Boolean type, () for the Unit type and  null for all object types.

上面的代码还演示了Scala中变量的表达方式,这个无需更多的解释大家都能清楚。不过值得注意的是我们给他赋予的初始值_ ,这个表示一个默认值,对于数字类型来说是0,对于boolean来说是false,对于Unit(函数签名)来说是() (无参数无返回),对于其他来说是null。

9  Conclusion                                                                                                                                                      15

To use this Reference class, one  needs to specify  which type to use for the type pa- rameter T, that is the  type  of the  element contained by the  cell.   For example, to create and  use a cell holding an integer, one could write the following:

要使用这个Reference 类,你需要制定他的类型参数,来告知这个引用到底引用了什么类型。例如要创建一个指向Int的引用,你可以这么写:

object IntegerReference {

def main(args: Array[String]) {

val cell = new Reference[Int] cell.set(13)

println(“Reference contains the half of ” + (cell.get * 2))

}

}

As can be seen in that example, it is not necessary to cast the value returned by the get method before using  it as an integer. It is also not possible to store anything but an integer in that particular cell, since  it was declared as holding an integer.

就像我们看到的,我们不需要吧get的返回值强制转换成Int,而且由于它被声明成Int,你不可能在这个引用中放置其他类型的对象。

9    Conclusion 结语

This document gave a quick overview of the Scala language and presented some ba- sic examples. The interested reader can go on by reading the companion document Scala By Example, which contains much more advanced examples, and  consult the Scala Language Specification when needed.

本文简要介绍了Scala语言的一些特性,并且同时展示了若干实例。有兴趣的读者可以继续阅读本文的姊妹篇:《Scala By Example》,该文覆盖了Scala的更多的高级特性。如果需要还可以去阅读《Scala Language Specification》。

>_<|| 我手贱!!这个有问题!绝对有问题!!一般般啦,真的很一般般。还不错哦~小表扬一下!GJ!乃就是新世界的神様了,快去拯救世界吧! (1 votes, average: 1.00 out of 5)
Loading...

2 人次吐槽

  1. liang3404814说道:
    骑着 Opera Mini 4.2.13337 Opera Mini 4.2.13337 和 J2ME/MIDP Device J2ME/MIDP Device
    Opera/9.60 (J2ME/MIDP; Opera Mini/4.2.13337/504; U; ja) Presto/2.2.0

    很好很强大,可谓jvm砖家。
    英语和文笔都不错嘛……纳闷你为啥上次讲ruby的视频都看不懂呢,好歹你还接触ruby的,我可是一片空白

  2. ssword说道:
    骑着 Google Chrome 2.0.172.33 Google Chrome 2.0.172.33 和 Windows XP Windows XP
    Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.5 (KHTML, like Gecko) Chrome/2.0.172.33 Safari/530.5

    支持下 ^_^
    到现在才正二八经看了下scala,感觉这门语言的特性都挺牛逼的,就是特性有点多…

春菜 对话 相声
双击调戏
双击调戏