Scala语法详解之表达式_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 2999 | 回复: 0   主题: Scala语法详解之表达式        下一篇 
wei.wang
注册用户
等级:少校
经验:1001
发帖:87
精华:0
注册:2013-8-29
状态:离线
发送短消息息给wei.wang 加好友    发送短消息息给wei.wang 发消息
发表于: IP:您无权察看 2013-9-6 10:53:29 | [全部帖] [楼主帖] 楼主

6. 表达式

语法:

Expr

Expr1

::= (Bindings | id | „_‟) „=>‟ Expr

| Expr1

::= „if‟ „(‟ Expr „)‟ {nl} Expr [[semi] else Expr]

| „while‟ „(‟ Expr „)‟ {nl} Expr

| „try‟ „{‟ Block „}‟ [catch „{‟

CaseClauses „}‟] [„finally‟ Expr]

| „do‟ Expr [semi] „while‟ „(‟ Expr „)‟

| „for‟ („(‟ Enumerators „)‟ |

„{‟ Enumerators „}‟) {nl} [„yield‟] Expr

| „throw‟ Expr

| „return‟ Expr

| [SimpleExpr „.‟] id „=‟ Expr

| SimpleExpr1 ArgumentExprs „=‟ Expr

| PostfixExpr

| PostfixExpr Ascription

| PostfixExpr „match‟ „{‟ CaseClauses „}‟

PostfixExpr ::= InfixExpr [id [nl]]

InfixExpr

::= PrefixExpr

| InfixExpr id [nl] InfixExpr

PrefixExpr ::= [„-‟ | „+‟ | „~‟ | „!‟] SimpleExpr

SimpleExpr ::= „new‟ (ClassTemplate | TemplateBody)

| BlockExpr

| SimpleExpr1 [„_‟]

SimpleExpr1 ::= Literal

| Path

| „_‟

| „(‟ [Exprs [„,‟] „]‟

| SimpleExpr „.‟ ids

| SimpleExpr TypeArgs

| SimpleExpr1 ArgumentExprs

| XmlExpr

Exprs

BlockExpr

::= Expr {„,‟ Expr}

::= „{‟ CaseClauses „}‟

| „{‟ Block „}‟

53

表达式类型化

Block

::= {BlockStat semi} [ResultExpr]

| (Bindings | (id | „_‟) „:‟ CompoundType) „=>‟ Block

ResultExpr ::= Expr1

Ascription ::= „:‟ InfixType

| „:‟ Annotation {Annotation}

| „:‟ „_‟ „*‟

表达式由算符和操作数构成。以下将按照以上顺序的降序来讨论表达式的形式。

6.1.

表达式类型化

     表达式的类型化往往和某些期望类型有关(可能是没有定义的)。当我们说“表达式 e

期望与类型 T 一致”时,我们的意思是(1)e 的期望类型是 T,(2)表达式 e 的类型必须与

T 一致。

     以下斯科伦化规则通用于所有表达式:如果一个表达式的类型是既存类型 T, 那么表

达式的类型就假定是 T 的斯科伦化(§3.2.10)。

     斯科伦化由类型打包反转。假定类型为 T 的表达式 e,且

t1[tps1]>:L1<:U1,...,tn[tpsn]>:Ln<:Un 是由 e 的一部分(在 T 中是自由的)的斯科

伦化所创建的所有类型变量。e 的打包类型是

T forSome { type t1[tps1]>:L1<:U1;...; type tn[tpsn]>:Ln<:Un }
6.2.


语法:

字面值

SimpleExpr ::= Literal


字面值的类型化如(§1.3)中所述;它们的求值是立即可得的。

字面值的另外一个形式指明类。形式如下:

classOf[C]


这里 classOf 是在 scala.Predef(§12.5)中定义的一个方法,C 是一个类类型。

该类字面值的值是类类型 C 的运行时表示。

6.3.

Null 值

     Null 值 的 类 型 是 scala.Null , 且 与 所 有 引 用 类 型 兼 容 。 它 表 示 一 个 指 向 特

殊”null”对象的引用值。该对象对类 scala.AnyRef 中的方法的实现如下:

     l eq(x)和==(x)返回 true 的条件是 x 同样也是一个”null”对象

     l ne(x)和!=(x)返回 true 的条件是 x 不是一个”null”对象

     l isInstanceOf[T]总返回 false

     l asInstanceOf[T]返回”null”对象的条件是 T 与 scala.AnyRef 一致,否则

     会抛出 NullPointerException。

     “null”对象对任何其他成员的引用将导致抛出一个 NullPointerException。

54


表达式

6.4.

语法:

指示器

SimpleExpr ::= Path
| SimpleExpr „.‟ id


   指示器指向一个命名术语。可以是一个简单命名或一个选择。

     简单命名 x 指向(§2)中所表示的一个值。如果 x 由一个封闭类或对象 C 中的定义或

声明绑定,那么它将等价于选择 C.this.x,C 指向包含 x 的类,即使类型名 C 在 x 出现

时是是被遮盖的(§2)。

     如果 r 是类型 T 的稳定标识符(§3.1),则选择 r.x 静态指向 r 的在 T 中以命名 x 标

识的术语成员 m。

     对于其他表达式 e,e.x 以{ val y = e; y.x }的形式类型化,y 是一个新命名。

代码块的类型化规则暗示在此情况下 x 的类型可能并不指向 e 的任何抽象类型成员。

     指示器前缀的期望类型总是未定义的。指示器的类型是其指向的实体的类型 T,但以

下情况除外:在需要稳定类型(§3.2.1)的上下文中路径(§3.1)p 的类型是单态类型

p.type。


   需要稳定类型的上下文需要满足以下条件:

     1. 路径 p 以一个选择前缀的形式出现,且并不表示一个常量,或

     2. 期望类型 pt 是一个稳定类型,或

     3. 期望类型 pt 是以一个稳定类型为下界的抽象类型,且 p 指向的实体的类型 T 与

     pt 不一致,或

     4. 路径 p 指向一个模块

     选择 e.x 在限定表达式 e 第一次求值时求值,同时产生一个对象 r。选取的结果是 r

的成员要么由 m 定义或由重载 m 的定义所定义。如果该成员具有与 scala.NotNull 一致

的 类 型 , 该 成 员 的 值 必 须 初 始 化 为 与 null 所 不 同 的 值 , 否 则 将 抛 出

scala.UnitializedError。
6.5.


语法:

This 和 Super

SimpleExpr ::= [id „.‟] „this‟
| [id „.‟] „super‟ [ClassQualifier] „.‟ id


表达式 this 可以出现在作为模板或复合类型的语句部分中。它表示由最里层的模板

或最靠近引用的复合类型所定义的对象。如果是一个复合类型,那么 this 的类型就是该

复合类型。如果是一个实例创建表达式的模板,this 的类型就是该模板的类型。如果是

一个有简单命名 C 的类或对象定义的模板,this 的类型与 C.this 的类型相同。

表达式 C.this 在具有简单命名 C 的封闭类或对象定义的语句部分中是合法的。它表

示由最里层该定义所定义的对象。如果表达式的期望类型是一个稳定类型,或 C.this 以

一个选择前缀的形式出现,那么它的类型就是 C.this.type,否则就是 C 自己的类型。

引用 super.m 静态地引用包含该引用的最里层模板的最小合理超类型的方法或类型 m。

它求值的结果等价于 m 或重载 m 的该模板的实际超类型的成员 m‟。被静态引用的成员 m

必须是类型或方法。如果是方法则必须是实体方法,如果是模板则必须是包含拥有重载了

55

函数应用

m 且标记为 abstract override 的成员 m‟的引用。

     引用 C.super.m 静态地引用包含该引用的最里层命名为 C 的封闭类或对象的定义中

最小合理超类型的方法或类型 m。该引用的求值为该类或对象的实际超类型中等价于 m 或

重载了 m 的成员 m‟。如果静态引用的成员 m 是一个方法,那么就必须是一个实体方法,

或 者最 内层名 为 C 的 封闭 类或 对象定 义必 须有一 个重 载了 m 且 标记为 abstract

override 的成员 m‟。

     前缀 super 可以后跟特征限定[T],比如 C.super[T].x。这叫做静态超引用。在

此情况下该引用指向具有简单名称 T 的 C 的父特征中的类型或方法 x。该成员必须具有唯

一定义。如果这是一个方法,则该方法必须是实体的。

示例 6.5.1 考虑以下类定义

class Root { def x = “Root” }
class A extends Root {
      override def x = “A”;
      def superA = super.x;
}
trait B extends Root {
      override def x = “B”;
      def superb = super.x;
}
class C extends Root with B {
      override def x = “C”;
      def superC = super.x;
}
class D extends A with B {
      override def x = “D”;
      def superD = super.x;
}


类 C 的线性化为{C, B, Root},类 D 的线性化为{D, B, A, Root}。那么我们有:

(new A).superA == “Root”
(new C).superB == “Root”
(new C).superC == “B”
(new D).superA == “Root”
(new D).superB == “A”
(new D).superD == “B”


要注意到 superB 函数根据 B 与类 Root 或 A 混用将返回不同的值。

6.6.

语法:

函数应用

::= SimpleExpr1 ArgumentExprs
::= „(‟ [Exprs [„,‟]] „)‟
| „(‟ [Exprs „,‟] PostfixExpr „:‟ „_‟ „*‟ „)‟
SimpleExpr
ArgumentExprs
56


表达式

| [nl] BlockExpr
Exprs
::= Expr {„.‟ Expr}


    应用 f(e1,..,en)将函数 f 应用于参量表达式 e1,...,en。如果 f 具有方法类型

(T1,...,Tn)U,则每个参量表达式 ei 的类型必须与对应的参数类型 Ti 一致。如果 f 具

有值类型,该应用则等价于 f.apply(e1,..,en),例如应用一个 f 定义的 apply 方法。

     f(e1,...,en)的求值通常必须按照 f 和 e1,...,en 的顺序来进行。每个参量表达式

将会被化为其对应的正式参数的类型。在此之后,该应用将会写回到函数的右侧,并用真

实参量来替换正式参数。被重写的右侧的求值结果将最终转变为函数声明的结果类型(如

果有的话)。

     函数应用通常会在程序运行时堆中定位一个新帧。然而如果一个本地函数或者一个

final 方法的最后动作是调用其自身的话,该调用将在调用者的堆栈帧中执行。

     对于具有无参方法类型=>T 的正式参数将会做特殊处理。该情况下,对应的实际参量

表达式并不会在应用前求值。相反地,重写规则中每次在右侧使用正式参数时都将会重新

对 e 求值。换句话说,对=>-参数的求值顺序是叫名的,而对于普通参数是传值的。同时

e 的打包类型(§6.1)必须与参数类型 T 一致。

     应用中最后一个参数可以标记为序列参数,例如 e:_*。这样的一个参数必须与一个

类型 S*的重复参数(§4.6.2)一致,也必须是唯一与该参数匹配的参量(比如正式参数与

实际参量必须在数目上匹配)。更进一步的,对于与 S 一致的某些类型 T,e 的类型必须与

scala.Seq[T]一致。在此情况下,参数类型的最终形式是用其元素来替换序列 e。

示例 6.6.1 设有以下函数来计算参数的总和:

def sum(xs: Int*) = (0 /: xs)((x, y) => x + y)


那么

sum(1,2,3,4)
sum(List(1,2,3,4): _*)


都会得到结果 10.然而

sum(List(1,2,3,4))


却无法通过类型检查。

6.7.

语法:

方法值

SimpleExpr ::= SimpleExpr1 „_‟


表达式 e _在 e 是方法类型或 e 是一个叫名参数的情况下是正确的。如果 e 是一个

具有参数的方法,e _ 表示通过 eta 展开(§6.25.5)得到的一个函数类型。如果 e 是一

个无参方法或者有类型=>T 的叫名参数,e _ 表示类型为() => T 的函数,且将在应用

于空参数列表()时对 e 求值。

示例 6.7.1 左列的方法值将对应等价于其右侧的匿名函数(§6.23)

Math.sin _
x => Math.sin(x)
57


类型应用

Array.range _
List.map2 _
List.map2(xs, ys)_
(x1, x2) => Array.range(x1, x2)
(x1, x2) => (x3) => List.map2(x1, x2)(x3)
x=> List.map2(xs, ys)(x)


要注意到在方法名和其后跟下划线间必须要有空格,否则下划线将会被认为是方法名

的一部分。

6.8.

语法:

类型应用

SimpleExpr ::= SimpleExpr TypeArgs
类型应用 e[T1,...,Tn]实例化了具有类型[a1 >: L1 <: U1,...,an >: Ln <:


Un]S 和参量类型 T1,...,Tn 的多态值 e。每个参量类型 Ti 必须符合对应的边界 Li 和 Ui。

也 就 是 对 于 每 个 i=1,...,n , 我 们 必 须 有 pLi <: Ti <: pUi , 这 里 p 是

[a1:=T1,...,an:=Tn]的指代。应用的类型是 pS。

     如果函数部分 e 是某种值类型,该类型应用将等价于 e.apply[T1,...,Tn],比如

由 e 定义的 apply 方法的应用。

     如果本地类型推断(§6.25.4)可以通过实际函数参量类型和期望结果类型来得到一个

多态函数的最佳类型参数,则类型应用可以忽略。

6.9.

语法:

元组

SimpleExpr ::= „(‟ [Exprs [„,‟]] „)‟


元组表达式(e1,...,en)是类实例创建 scala.Tuplen(e1,...,en)的别名(n>=2)。

该表达式后面还可以有个分号,例如(e1,...,en,)。空元组()是类型 scala.Unit 的唯

一值。

6.10. 实例创建表达式

语法:

SimpleExpr ::= „new‟ (ClassTemplate | TemplateBody)
一个简单的实例创建表达式具有形如 new c 的形式,c 是一个构造器调用(§5.1.1)。


设 T 为 c 的类型,那么 T 必须表示 scala.AnyRef 的一个非抽象子类(的类型实例)。更

进一步,表达式的固实自类型必须与 T 表示的类型的自类型一致(§5.1)。固实自类型通

常为 T,一个特例是表达式 new c 在值定义的右侧出现

val x: S = new c


   (类型标注: S 可能没有)。在此情况下,表达式的固实自类型是复合类型 T with

x.type。


   该表达式的求值方式是通过创建一个类型为 T 的新对象并以对 C 求值来初始化。表达

58


表达式

式的类型为 T。

对于某些类模板 t(§5.1),一个常见的实例创建表达式具有 new t 的形式。这样的

表达式等价于代码块

{ class a extends t; new a }


a 是匿名类的一个新名称。

创建结构化类型的值的快捷方式为:如果{D}是一个类体,则 new {D}就等价于通用

实例创建表达式 new AnyRef{D}

示例 6.10.1 考虑以下结构化实例创建表达式

new { def getName() = “aaron” }


这是以下通用实例创建表达式的简写形式

new AnyRef{ def getName() = “aaron” }


后者则是以下代码块的简写:

{
class anon$X extends AnyRef{ def getName() = “aaron” };
new anon$X;
}


这里 anon$X 是某个新创建的名称。

6.11. 代码块

语法:

BlockExpr
Block
::= „{‟ Block „}‟
::= {BlockStat semi} [ResultExpr]


  代码块表达式{s1;...;sn;e}由一个代码块语句序列 s1,...,sn 和一个最终表达式

e 构成。语句序列中不能有两个定义或声明绑定到同一命名空间的同一命名上。最终表达

式可忽略,默认为单元值()。

     最终表达式 e 的期望类型是代码块的期望类型。所有前面的语句的期望类型是未定义

的。

     代码块 s1;...;sn;e 的类型是 T forSome {Q},T 是 e 的类型,Q 包括在 T 中每

个自由的和在语句 s1,...,sn 中局部定义值或类型命名的既存类型(§3.2.10)。我们说

存在子句绑定了值或者类型命名。需要特别指出的:

     l 一 个 本 地 定 义 的 类 型 定 义 type t[tps]=T 由 存 在 子 句 clause

     type[tps]>:T<:T 绑定

     l 一个本地定义的值定义 val x:T=e 由存在子句 val x:T 绑定

     l 一 个 本 地 定 义 的 类 定 义 class c[tps] extends t 由 存 在 子 句 type

     c[tps]<:T 绑定,T 是最小类类型或修饰类型,且是类型 c[tps]的合适超类。

     l 一个本地定义的对象定义 object x extends t 由存在子句 val x:T 绑定,

     T 是最小类类型或修饰类型,且是类型 x.type 的合适超类。

     对代码块求值需要对其语句序列求值,然后对最终表达式 e 求值,该表达式定义了代

59


前缀,中缀及后缀运算

码块的结果。

示例 6.11.1 假定有类 Ref[T](x: T), 代码块

{ class C extends B {...} ; new Ref(new C) }


具有类型 Ref[_1] forSome { type _1 <: B }. 代码块

{ class C extends B {...} ; new C }


   的类型仅是 B,因为(§3.2.10)中的规则有存在限定类型 _1 forSome { type

_1 <: B }可简化为 B。

6.12. 前缀,中缀及后缀运算

语法:

PostfixExpr ::= InfixExpr [id [nl]]
InfixExpr
::= PrefixExpr
| InfixExpr id [nl] Inf2424ixExpr
PrefixExpr ::= [„-‟ | „+‟ | „!‟ | „~‟] SimpleExpr


表达式由算符和操作数构成。

6.12.1. 前缀运算

     前缀运算 op e 由前缀算符 op(必须是„+‟, „-‟, „!‟或„~‟之一)。表达式 op e

等价于后缀方法应用 e.unary_op。

     前缀算符不同于普通的函数应用,他们的操作数表达式不一定是原子的。例如,输入

序列-sin(x)读取为-(sin(x)),函数应用 negate sin(x)将被解析为将中缀算符

sin 应用于操作数 negate 和(x)。

6.12.2. 后缀操作

后缀算符可以是任意标识符。后缀操作 e op 被解释为 e.op。

6.12.3. 中缀操作

中缀算符可以是任意标识符。中缀算符的优先级和相关性定义如下:

中缀算符的优先级由算符的第一个字符确定。字符按照优先级升序在下面列出,同一

行中的字符具有同样的优先级。

(所有字母)
|
^
&
<>
=!
:
60


表达式

+-
* /%
(所有其他特殊字符)


   也就是说,由字母开头的算符具有最低的优先级,然后是由„|‟开头的算符,下同。

     这个规则中有一个例外,就是赋值算符(§6.12.4)。赋值算符的优先级与简单赋值

(=)相同。也就是比任何其他算符的优先级要低。

     算符的相关性由算符的最后一个字符确定。由„:‟结尾的算符是右相关的。其他所有

算符是左相关的。

     算符的优先级和相关性确定了表达式部件结组的方式:

     l 如果表达式中有多个中缀运算,那么具有高优先级的算符将比优先级低的绑定的

     更紧。

     l 如果具有连贯的中缀运算 e0 op1 e1 op2...opn en,且算符 op1,...,opn 具

     有同样的优先级,那么所有的这些算符将具有同样的相关性。如果所有算符都是

     左相关的,该序列将解析为(...(e0 op1 e1) op2...) opn en。否则,如果

     所有算符都是右相关的,则该序列将解析为 e0 op1(e1 op2 (...opn en)...)

     l 后缀算符的优先级总是比中缀算符低。例如 e1 op1 e2 op2 总是等价于(e1 op1

e2) op2。


   左相关算符的右侧操作数可以由在括号中的几个参数组成,例如 e op(e1,...,en)。

该表达式将被解析为 e.op(e1,...,en)。

     左相关位运算 e1 op e2 解析为 e1.op(e2)。如果 op 是右相关的,同样的运算将被

解析为{ val x=e1; e2.op(x) },这里 x 是一个新的名称。

6.12.4. 赋值算符

赋值算符是一个由等号“=”结尾的算符记号((§1.1)中的语法类 op),但具有以下

条件的算符除外:

(1)算符也由等号开始,或

(2)算符是(<=), (>=), (!=)中的一个

赋值算符做特殊处理,如果没有其它有效的解释,则扩展为赋值。

我们考虑一个赋值算符,比如+=。在中缀运算 l += r 中,l 和 r 是表达式。该运算

可以重新解释为负责赋值的运算

l=l+r


除非该运算的做的 l 只计算一次。

在以下两种条件下会发生再解析。

1. 左侧的 l 没有一个名为+=的成员,且不能由隐式转换(§6.25)转换为拥有成员+=

     的值。

2. 赋值运算 l = l + r 是类型正确的。特别此处暗含了 l 引用了一个变量或对象,

     且该变量或对象可以赋值,且可转变为一个具有名为+的成员的值。

6.13. 类型化的表达式

语法:

Expr1
::= PostfixExpr „:‟ CompoundType
61


标注表达式

     类型化的表达式 e:T 具有类型 T。表达式 e 的类型被期望与 T 一致。表达式的结果就

是 e 的值转化为类型 T。

示例 6.13.1 以下是合法与非法类型化的表达式的例子

1: Int
1: Long
//1: string
//合法,类型为 Int
//合法,类型为 Long
//*****非法


6.14. 标注表达式

语法:

Expr1
::= PostfixExpr „:‟ Annotation {Annotation}


标注表达式 e: @a1 ... @an 将标注 a1,...,an 附在表达式 e(§11)上。

6.15. 赋值

语法:

Expr1
::= [SimpleExpr „.‟] id „=‟ Expr
| SimpleExpr1 ArgumentExpr „=‟ Expr


   对一个简单变量的赋值 x = e 的解释依赖于 x 的定义。如果 x 是一个可变量,那么

赋值将把当前 x 的值变为对表达式 e 求值所得的结果。e 的类型被期望与 x 的类型一致。

如果 x 是某些模板中定义的无参数函数,且该模板中包括一个 setter 函数 x_=成员,那

么赋值 x = e 就解释为对该 setter 函数的调用 x_=(e)。类似地,赋值 f.x = e 应用

于一个无参函数 x 就解释为调用 f.x_=(e)。

     赋值 f(args) = e 中=算符左侧的函数应用解释为 f.update(args, e),例如对

由 f 定义的 update 函数的调用。

示例 6.15.1 以下是矩阵乘法中的一些常用代码

def matmul(xss: Array[Array[Double]], yss: Array[Array[Double]]) = {
      val zss: Array[Array[Double]] = new Array(xss.length, yss(0).length)
      var I = 0
      while (I < xss.length) {
            var j = 0
            while (j < yss(0).length) {
                  var acc = 0.0
                  var k = 0
                  while (k < yss.length){
                        acc = acc + xss(i)(k) * yss(k)(j)
                        k += 1
                  }
                  zss(i)(j) = acc
                  j += 1
            }
            62


表达式

i += 1
}
zss
}


去掉数据访问和赋值的语法糖,则是下面这个扩展的版本:

def matmul(xss: Array[Array[Double]], yss: Array[Array[Double]]) = {
      val zss: Array[Array[Double]] = new Array(xss.length,
      yss.apply(0).length)
      var I = 0
      while (I < xss.length) {
            var j = 0
            while (j < yss.apply(0).length) {
                  var acc = 0.0
                  var k = 0
                  while (k < yss.length){
                        acc = acc + xss.apply (i) .apply (k) * yss.apply (k) .apply (j)
                        k += 1
                  }
                  zss.apply (i).update(j, acc)
                  j += 1
            }
            i += 1
      }
      zss
}


6.16. 条件表达式

语法:

Expr1
::= „if‟ „(‟ Expr „)‟ {nl} Expr [[semi] „else‟ Expr]


    条件表达式 if (e1) e2 else e3 根据 e1 的值来选择值 e2 或 e3。条件 e1 期望与类

型 Boolean 一致。Then 部分 e2 和 else 部分 e3 都期望与条件表达式的期望类型一致。

条件表达式的类型是 e2 和 e3 的类型的最小上界。else 前的分号会被忽略。

     条件表达式的求值中首先对 e1 求值。如果值为 true,则返回 e2 求值的结果,否则返

回 e3 求值的结果。

     条件表达式的一种简单形式没有 else 部分。条件表达式 if (e1) e2 求值方式为 if

(e1) e2 else ()。该表达式的类型是 Unit,且 then 部分 e2 也期望与类型 Unit 一致。

6.17. While 循环表达式

语法:

Expr1
::= „while‟ „(‟ Expr „)‟ {nl} Expr
63


Do 循环表达式

     While 循环表达式 while(e1)e2 的类型化与求值方式类似于函数 whileLoop (e1)

(e2)的应用,假定的函数 whileLoop 定义如下:

def whileLoop(cond: => Boolean)(body: => Unit): Unit =
if (cond) { body ; whileLoop(cond)(body) } else {}


6.18. Do 循环表达式

语法:

Expr1
::= „do‟ Expr [semi] „while‟ „(‟ Expr „)‟


   Do 循 环表达 式 do e1 while (e2) 的类 型化 与求 值方 式类似 于表 达式 (e1 ;

while (e2) e1)。Do 循环表达式中 while 前的分号被忽略。

6.19. For 语句段

语法:

Expr1
::= „for‟ („(‟ Enumerators „)‟ | „{‟ Enumerators
„}‟) {nl} [„yield‟] Expr
Enumerators ::= Generator {semi Enumerator}
Enumerator ::= Generator
| Guard
| „val‟ Pattern1 „=‟ Expr
Generator
Guard
::= Pattern1 „<-‟ Expr [Guard]
::= „if‟ PostfixExpr


  for 语句段 for (enums) yield e 对于由枚举器 enums 产生的每个绑定求值表达

式 e。一个枚举器序列总是由一个产生器开始;然后可跟其他产生器,值定义,或守卫。

一个产生器 p <- e 从一个与模式 p 匹配的表达式 e 产生绑定。值定义 val p = e 将值

名称 p(或模式 p 中的数个名称)绑定到表达式 e 的求值结果上。守卫 if e 包含一个布尔

表达式,限制了枚举出来的绑定。产生器和守卫的精确含义通过翻译为四个方法的调用来

定义:map filter flatMap 和 foreach。这些方法可以针对不同的携带类型具有不同

的实现。

     翻译框架如下。在第一步里,每个产生器 p <- e,对于 e 的类型被替换为如下形式,

p 不是不可反驳的(§8.1):

p <- e.filter { case p => true; case _ => false }


然后,以下规则将重复应用,直到所有的语句段都消耗完毕。

l for 语句段 for (p <- e) yield e‟被翻译为 e.map { case p => e‟ }

l for 语句段 for (p <- e) e‟ 被翻译为 e.foreach { case p => e‟ }

l for 语句段

for (p <- e; p‟ <- e‟ ...) yield e‟‟,


这里...是一个产生器或守卫序列(可能为空),该语句段翻译为

e.flatMap { case p => for(p‟ <- e‟ ...) yield e‟‟ }
l
64


for 语句段

for (p <- e; p‟ <- e‟ ...) e‟‟


表达式

这里... 是一个产生器或守卫序列(可能为空),该语句段翻译为

e.foreach { case p => for (p‟ <- e‟ ...) e‟‟ }
l
l


后 跟 守 卫 if g 的 产 生 器 p <- e 翻 译 为 单 个 产 生 器 p <-

e.filter((x1,...,xn) => g),这里 x1,...,xn 是 p 的自由变量。

后跟值定义 val p‟ = e‟的产生器 p <- e 翻译为以下值对产生器,这里的 x

和 x‟是新名称:

val (p, p‟) <-
for (x@p <- e) yield { val x‟@p‟ = e‟; (x, x‟) }


示例 6.19.1 以下代码产生 1 到 n-1 间所有和为素数的数值对

for { i <- 1 until n
      j <- 1 until i
      if isPrime(i+j)
} yield (i, j)


该 for 语句段翻译为:

(1 until n)

.flatMap {
      case i => (1 until i)
.filter { j => isPrime(i+j) }
.map { case j => (i, j) }


示例 6.19.2 for 语句段可以用来简明地描述向量和矩阵算法。比如以下就是一个函数来

计算给定矩阵的转置:

def transpose[A](xss: Array[Array[A]]) = {
      for (i <- Array.range(0, xss(0).length)) yield
      for (xs <- xss) yield xs(i)
}


以下是一个函数,用来计算两个向量的无向量积:

def scalprod(xs: Array[Double], ys: Array[Double]) = {
      var acc = 0.0
      for ((x, y) <- xs zip ys) acc = acc + x * y
      acc
}


最后,这是一个求两个矩阵的积的函数。可以与示例 6.15.1 中的常见版本做一个比


def matmul(xss: Array[Array[Double]], yss: Array[Array[Double]] = {
      val ysst = transpose(yss)
      for (xs <- xss) yield
      for (yst <- ysst) yield
      scalprod(xs, yst)
}
65


Return 表达式

     以上代码使用了类 scala.Array 中已有定义的成员 map, flatMap, filter 和

foreach。


6.20. Return 表达式

语法:

Expr1
::= „return‟ [Expr]


   return 表达式 return e 必须出现在某些封闭的命名方法或函数体内。源程序中最

里层的封闭命名方法或函数 f 必须有一个显式声明的结果类型,e 的类型必须与其一致。

return 表达式求值表达式 e 并返回其值作为 f 的结果。任何 return 表达式之后的语句

或表达式将忽略求值。return 表达式的类型是 scala.Nothing。表达式 e 可以没有。

表达式 return 以 return ()的形式做类型检查和求值。

     由编译器生成的 apply 方法,作为匿名函数的扩展,并不能作为源程序中的命名函数,

因此不是 return 表达式的目标。

     如果 return 表达式自身是匿名函数的一部分,可能在 return 表达式被求值前 f 的

封闭实体就已经返回了。在此情况下会抛出

scala.runtime.NonLocalReturnException 异常。

6.21. Throw 表达式

语法:

Expr1
::= „throw‟ Expr


   throw 表达式 throw e 对表达式 e 求值。该表达式的类型必须与 Throwable 一致。

如果 e 求值的结果是异常的引用,则求值结束,抛出该异常。如果 e 求值的结果是 null,

则求值结束并抛 出 NullPointerException。如果此处有一个 活动的 try 表达式

(§6.22),且要处理抛出的异常,则求值在该处理器中继续进行;否则执行 throw 的线

程将被终止。throw 表达式的类型是 scala.Nothing。

6.22. Try 表达式

语法:

Expr1
::= „try‟ „{‟ Block „}‟ [„catch‟ „{‟ CaseClauses „}‟]
[„finally‟ Expr]


   Try 表达式具有 try { b } catch h 的形式,处理器 h 是能匹配以下匿名函数

(§8.5)的模式

{ case p1 => b1 ... case pn => bn}


  该表达式的求值方式是对代码块 b 求值。如果 b 的求值并没有导致抛出异常,则返回

b 的结果。否则处理器 h 将应用于抛出的异常。如果处理器包含一个 case 与抛出的异常

匹配,则调用第一个该类 case。如果没有与抛出的异常匹配的 case,则异常被重新抛出。

     设 pt 是 try 表达式的期望类型。代码块 b 被期望与 pt 一致。处理器 h 期望与类型

66

表达式

scala.PartialFunction[scala.Throwable, pt]一致。try 表达式的类型是 b 的

类型与 h 的结果类型的最小上界。

     try 表达式 try{ b } finally e 首先对代码块 b 求值。如果在求值中没有导致异

常抛出,则对表达式 e 求值。如果在对 e 求值中有异常抛出,则 try 表达式的求值终止并

抛出异常。如果在对 e 求值时没有异常抛出,则返回 b 的结果作为 try 表达式的结果。

     如果在对 b 求值时有异常抛出,finally 代码块 e 同样也会被执行。如果在对 e 求

值时有另外一个异常被抛出,则 try 表达式求值终止,同时抛出该异常。如果在对 e 求值

时没有异常抛出,b 中抛出的异常在 e 的求值终止时被重新抛出。代码块 b 被期望与 try

表达式所期望的类型一致。finally 表达式 e 被期望与类型 Unit 一致。

     Try 表达式 try { b } catch e1 finally e2 是 try { try { b } catch

e1 } finally e2 的简写。

6.23. 匿名函数

语法:

Expr
Bindings
Binding
::= (Bindings | Id | „_‟) „=>‟ Expr
::= „(‟ Binding {„,‟ Binding} „)‟
::= (id | „_‟) [„:‟ Type]
ResultExpr ::= (Bindings | (Id | „_‟) „:‟ CompoundType) „=>‟ Block


   匿名函数(x1: T1,...,xn: Tn) => e 将类型为 Ti 的参数 xi 映射为由表达式 e 给

出的结果。每个正式参数 xi 的作用域是 e。正式参数必须具有两两不同的名称。

     如果匿名函数的期望类型具有 scala.Functionn[S1,...,Sn, R]的形式,则 e 的

期望类型是 R,每个参数 xi 的类型 Ti 可忽略,可假定 Ti = Si。如果匿名函数的期望类

型是某些其他类型,则所有正式参数的类型都必须显式的给出,且 e 的期望类型是未定义

的。匿名函数的类型是 scala.Functionn[S1,...,Sn, T],这里 T 是 e 的打包类型

(§6.1)。T 必须等价于一个不引用任何正式参数 xi 的类型。

     匿名函数求值方式为实例创建表达式

new scala.Functionn[T1,...,Tn, T] {
      def apply(x1: T1,...,xn: Tn): T = e
}


   在具有单个未类型化的正式参数时,(x) => e 可缩写为 x => e。如果匿名函数

(x: T) => e 有单个类型化的参数作为一个代码块的结果表达式出现,则可缩写为 x: T

=> e。


   一个正式参数也可以是由一个下划线 _ 表示的通配符。在此情况下可以随意选择该

参数的一个新名称。

示例 6.23.1 匿名函数的例子

x => x
f => g => x => f(g(x))
(x: Int, y: Int) => x + y
() => { count +=1; count }
//恒等函数
//柯里化的函数组合
//求和函数
//该函数参数列表为空
//将一个非本地变量‟count‟加 1
//并返回新的值
67


语句

_ => 5
//该函数忽略其所有参数并总是返回 5


匿名函数的占位符语法

语法:

SimpleExpr1 ::= „_‟


  一个表达式(语法上归类为 Expr)可以在合法的标识符处包含内嵌的下划线记号 _。

这样一个表达式表示一个下划线后的位置的连续的参数的匿名函数。

     定义下划线段具有形式为_:T 的表达式,T 是一个类型,或者形式为_,下划线并不是

类型归属_:T 的表达式部分。

     具有 Expr 句法归类的表达式 e 绑定到下划线段 u 的两个条件是:(1) e 合理包含 u,

且(2)没有其他的具有 Expr 句法归类的表达式合理包含于 e 且其自身合理包含 u。

     如 果 表 达 式 e 按 照 既 定 顺 序 绑 定 到 下 划 线 段 u1,...,un , 则 等 价 于 匿 名 函 数

(u‟1,...,u‟n)=> e‟,每个 u‟i 是将 ui 中下划线替换为新的标识符的结果,e‟则是将

e 中每个下划线段 ui 替换为 u‟i 的结果。

示例 6.23.2 左侧的匿名函数使用了占位符语法。每个都等价于其右侧的匿名函数

_+1
_*_
(_: Int) *2
if (_) x else y
_.map(f)
_.map(_ + 1)
x => x + 1
(x1, x2) => x1 * x2
(x: Int) => (x: Int) * 2
z => if (z) x else y
x => x.map(f)
x => x.map(y => y + 1)


6.24. 语句

语法:

BlockStat
::= Import
| [„implicit‟] Def
| {LocalModifier} TmplDef
| Expr1
|
TemplateStat
::= Import
| {Annotation} {Modifier} Def
| {Annotation} {Modifier} Del
| Expr
|


语句是代码块和模板的一部分。语句可以是 import,定义或表达式,也可为空。在

模板或类定义中使用的语句还可以是声明。作为语句来使用的表达式可以有任意的值类型。

表达式语句 e 的求值是对表达式 e 求值然后丢弃求值的结果。

代码块语句可以是在代码块中绑定本地命名的定义。代码块本地定义中允许的修饰符

是类或对象定义前的 abstract, final, sealed。

语句序列的求值是语句按照它们书写顺序的求值。

68


表达式

6.25. 隐式转换

隐式转换可以应用于那些类型与期望类型不一致的表达式或未应用的方法。在接下来

的两小节中将给出可用的隐式转换。

如果 T 与 U 在应用 eta 扩展(§6.25.5)和视图应用(§7.3)后一致,则 T 与 U 是兼

容的。

6.25.1. 值转换

以下的五个隐式转换可以应用到具有类型 T,且对某些期望类型 pt 做了类型检查的表

达式 e。

重载解析 如果一个表达式表示某类的数个可能的成员,应用重载解析(§6.25.3)可以选

定唯一的成员。

类型实例化 表达式 e 具有多态类型

[a1 >: L1 <: U1,...,an >: Ln <: Un]T


   并 不 作 为 类 型 应 用 的 函 数 部 分 , 根 据 类 型 变 量 a1,...,an 通 过 本 地 类 型 推 断

(§6.25.4)来确定实例类型 T1,...,Tn ,并隐式将 e 嵌入类型应用 e[T1,...,Tn]

(§6.8)的方式转换为 T 的类型实例。

数字字面值缩减 如果期望类型是 Byte, Short 或者 Char,且表达式 e 是一个符合该类

型范围的整数字面值,则将被转换为该类型同样的字面值。

值丢弃 如果 e 具有值类型且期望类型是 Unit,则 e 通过嵌入术语{ e; () }的方式转

换为期望类型。

视图应用 如果没有应用以上任何转换,且 e 的类型与期望类型 pt 不相似,则将通过视图

(§7.3)尝试将 e 转换为期望的类型。

6.25.2. 方法转换

以下四个隐式转换可应用到那些无法应用到指定参数列表的方法上面。

求值 具有类型=> T 的无参数方法 m 总是通过对 m 绑定的表达式求值来转换到类型 T

隐式应用 如果该方法只接受隐式参数,则将通过规则§7.2 传递隐式参量。

eta 扩展 否则,如果方法不是一个构造器,且期望类型是一个函数类型(Ts‟)=>T‟,则

eta-扩展应用于表达式 e。

空应用 否则,如果 e 拥有方法类型()T,则将隐式应用于空参数列表,产生 e()。

6.25.3. 重载解析

69

隐式转换

     如果一个标识符或选择 e 引用了数个类的成员,则将使用引用的上下文来推断唯一的

成员。使用的方法将依赖于 e 是否被用作一个函数。设A是 e 引用的成员的集合。

     首先假定 e 作为函数出现在应用中,比如 e(args)。如果在A中有且仅有一个可选成

员是一个(可能是多态)方法类型,其元数与给出的参量数目匹配,则就会选定该可选成员。

     否则,设 Ts 是通过用未定义类型来类型化每个参量所得到的类型向量。首先要确定

的是可用的可选成员的集合。如果 Ts 中每个类型都与对应的可选成员中正式参数类型相

似,且如果期望类型已定义,方法的结果类型与之兼容,则该可选项是可用的。对于一个

多态方法,如果本地类型推断可以确定类型参量,则该实例化的方法是可用的,继而该多

态方法也是可用的。

     设B是可用的可选项的集合。如果B为空则导致错误。否则可以用以下”同样具体”和”

更具体” 的定义来选出在B中最具体的可选项:

     l 具有类型(Ts)U 的参数化的方法,如果有某些类型为 S 的其他成员,S 对于类型

     Ts 的参量(ps)是可用的,则该方法与这些成员同样具体。

     l 具有类型[a1 >: L1 <: U1,...,an >: Ln <: Un]T 的多态方法,如果有某

     些类型为 S 其他成员,如果假定对于 i=1,...,n,每个 ai 都是一个抽象类型命

     名,其边界在 Li 之上且在 Ui 之下,有 T 和 S 同样具体,则该方法和这些成员同

     样具体。

     l 具有其他类型的成员总是与一个参数化的方法或一个多态方法同样具体。

     l 给定具有类型 T 和 U 的两个没有参数化也不是多态方法类型的成员,类型为 T 的

     成员与类型为 U 的成员同样具体的条件是 T 的双重存在与 U 的双重存在相似。这

     里 多 态 类 型 [a1 >: L1 <: U1,...,an >: Ln <: Un]T 的 双 重 存 在 是 T

     forSome { type a1 >: L1 <: U1,...,type an >: Ln <: Un}。其他类

     型的双重存在是类型自身。

     如果 A 与 B 同样具体,同时要么 B 与 A 不同样具体,要么 A 在 B 的一个子类中定义,

则 A 比 B 更具体。

     如果 B 中没有可选项比 B 中其他可选项更具体则将导致错误。

     下面假定 e 以函数的形式在类型应用中出现,比如 e[targs]。那么我们将选择 A 中

所有的与 targs 中的类型参量具有同样数目的参数类型的可选项。如果没有该类可选项将

导致错误。如果有多个这样的可选项,则将对整个表达式 e[targs]重新应用重载解析。

     最后我们假定 e 没有在应用或类型应用中做为函数出现。如果给出了期望类型,设 B

是 A 中与其兼容(§6.25)的该类可选项的集合。否则,设 B 为 A。在此情况下我们在 B 的

所有可选项中选择最具体的可选项。如果 B 中没有可选项比 B 中其他所有的可选项更具体

则将导致错误。

     在所有情况下,如果最具体的可选项定义在类 C 中,且有另外一个可应用的可选项定

义在 C 的子类中,则将导致错误。

示例 6.25.1 考虑以下定义:

class A extends B {}
def f(x: B, y: B) = ...
def f(x: A, y: B) = ...
val a: A
val b: B


则应用 f(b, b)指向 f 的第一个定义,应用 f(a, a)指向第二个。假设我们添加第

三个重载定义

70


表达式

def f(x: B, y: A) = ...

则应用 f(a, a)将因模糊定义而被拒绝,因为不存在更具体的可应用签名。

6.25.4. 本地类型推断

     本地类型推断推测将要传递给多态类型的表达式的类型参数。比如 e 有类型[a1 >:

L1 <: U1,...,an >: Ln <: Un]T 且没有显式类型参数给出。

     本地类型推断将此表达式转换为一个类型应用 e[T1,...,Tn]。类型参量 T1,...,Tn

的选择依赖于表达式出现处的上下文和期望类型 pt。这里有三种情况。

第一种情况:选择 如果表达式作为名为 x 的命名的前缀出现,则类型推断被延后至整个

表达式 e.x。也就是如果 e.x 有类型 S,则现在处理的形式是有类型[a1 >: L1 <:

U1,...,an >: Ln <: Un]S,且本地类型推断应用到在 e.x 出现处的上下文中推断类型

参量 a1,...,an。

第二种情况:值 如果表达式作为值出现,且没有被应用值参量,类型参量推断的方式是求

解一个与表达式类型 t 和期望类型 pt 有关的限定系统。不失一般性,我们可以假定 T 是

一个值类型;如果它是一个方法类型,我们可应用 eta 扩展(§6.25.5)将其变为函数类

型。求解的意思是找到一个类型参数 ai 的一个类型为 Ti 的代换 σ,且有:

     l 遵守所有的类型参数边界,例如 σLi <: σai 且 σai <: σUi(i=1,...,n)

     l 表达式类型与期望类型相似,例如 σT <: σpt。

     如果没有这样的代换存在,则将导致编译时错误。如果存在数个这样的代换,则本地

类型推断将会针对每个类型变量 ai 的解空间选择一个最小或最大类型 Ti。如果类型参数

ai 在表达式的类型 T 中以逆变的形式出现,则选择最大类型 Ti。在其他情况中选择最小类

型 Ti,比如变量以协变,非变的方式在 T 中出现,或没有变量。这样的代换叫做类型为 T

的给定限定系统的最优解。

第三种情况:方法 如果表达式 e 在应用 e(d1,...,dn)中出现则应用该情况。此处 T 是

一个方法类型(R1,...,Rm)T‟。不失一般性我们可以假定结果类型 T‟是一个值类型;如

果这是一个方法类型,我们可以应用 eta 扩展(§6.25.5)将其变为函数类型。首先使用

两个代换方案计算参量表达式 dj 的类型 Sj。每个参量表达式 dj 首先用期望类型 Rj 类型化,

     在这里类型参数 a1,...,an 作为类型常量。如果失败的话,则将 Rj 中的每个类型参

数 a1,...,an 替换为未定义,得期望类型 R‟j,用此类型来类型化参量 dj。

     第二步,通过解一个与期望类型为 pt 的方法类型和参量类型 S1,...,Sm 有关的限定

系统来推断类型参量。求解该限定系统的意思是找到类型参数 ai 的类型 Ti 的代换 σ,有:

     l 遵守所有的类型参数边界,例如 σLi <: σai 且 σai <: σUi(i=1,...,n)

     l 方法的结果类型 T‟与期望类型一致,例如 σT‟ <: σpt

     l 每个参量类型与对应的正式参数类型一致,例如 σSj<:σRj(i=1,...,n)

     如果不存在该代换则将导致编译时错误。如果存在数个解,则选择类型 T‟的一个最优

解。

     期望类型 pt 的全部或部分可以是未定义的。在此一致性规则(§3.5.2)有所扩展,

对于任意类型 T 以下两个语句总是正确的

     undefined <: T 和 T <: undefined

     对于给定类型变量,可能不存在最小解或最大解,这将导致编译时错误。由于<:是前

71


隐式转换

序的,因此一个类型的解集中可以有多个最优解。在此情况下 Scala 编译器将自由选取其

中某一个。

示例 6.25.2 考虑以下两个方法

def cons[A](x: A, xs: List[A]): List[A]= x :: xs
def nil[B]: List[B] = Nil


以及定义:

val xs = cons(1, nil)


   cons 的应用首先由一个未定义的期望类型进行类型化。该应用通过本地类型推断为

cons[Int](1, nil)来完成。这里使用了以下理由来推断类型参数 a 的类型参量 Int:

     首先,参量表达式被类型化。第一个参量 1 的类型是 Int,第二个参量 nil 是自身多

态的。首先尝试用期望类型 List[a]对 nil 做类型检查。这将得到限定系统

List[b?] <: List[a]


b?中的问号指这是限定系统中的一个变量。因为类 List 是协变的,该限定的最优解

是:

b = scala.Nothing


第二步,在以下限定系统中求解 cons 的类型参数 a

Int <: a?
List[scala.Nothing] <: List[a?]
List[a?] <: undefined


该限定系统的最优解是

a = Int


所以 Int 就是 a 的类型推断的结果。

示例 6.25.3 考虑以下定义

val ys = cons(“abc”, xs)


xs 在前面定义了类型 List[Int]。在此情况下本地类型推断过程如下:

首先将参量表达式类型化。第一个参量“abc”的类型为 String。第二个参量 xs 首先

被尝试用期望类型 List[a]类型化。这会失败,因为 List[Int]不是 List[a]的子类型。

因此尝试第二种策略;将使用期望类型 List[undefined]来类型化 xs。这会成功得到

参量类型 List[Int]。

第二步,在以下限定系统中求解 cons 的类型参数 a:

String <: a?
List[Int] <: List[a?]
List[a?] <: undefined


该限定系统的最优解是

a = scala.Any
72


表达式

所以 scala.Any 就是 a 的类型推断的结果。

6.25.5. Eta 扩展

     Eta 扩展将一个方法类型的表达式变为一个等价的函数类型的表达式。由两步组成。

     首先,标识出 e 的最大子表达式;比如 e1,...,em。对于其中每项创建一个新命名

xi。设 e‟是将 e 中每个最大子表达式替换为对应的新命名 xi 得到的表达式。然后,为方

法的每个参量类型 Ti 创建一个新命名 yi(i=1,...,n)。eta 扩展的结果是:

{ val x1 = e1;
...
val xm = em;
(y1:T1,...,yn:Tn) => e‟(y1,...,yn)
}


如果 e 仅有一个叫名参数(例如有类型(=>T)U,T 和 U 为某些类型),e 的 eta 扩展

将产生一个类型为 ByNameFunction 的值,该类型定义如下:

trait ByNameFunction[-A, +B] extends AnyRef {
      def apply(x: => A): B
      override def toString = “<function>”
}


eta 扩展不适用于那些在一个参数段中既有叫名参数又有其他参数的方法。也不适用

于那些有重复参数 x: T*(§4.6.2)的方法。

73




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论