EPFL 編程方法實驗室css |
Scala 語言規範java |
版本:2.7git |
原做:馬丁.奧德賽 翻譯:高德 趙煒正則表達式
2010-7-20算法
1.1. 標識符 1編程
1.2. 換行字符 2數組
1.3. 字面值 5緩存
1.3.1. 整型字面值 5安全
1.3.2. 浮點型字面值 5數據結構
1.3.3. 布爾型字面值 6
1.3.4. 字符型字面值 6
1.3.5. 字符串字面值 6
1.3.6. 轉義序列 7
1.3.7. 記號字面值 8
1.4. 空白與註釋 8
1.5. XML 模式 8
3.2. 值類型 14
3.2.1. 單例類型 14
3.2.2. 類型映射 14
3.2.3. 類型指示 15
3.2.4. 參數化類型 15
3.2.5. 元組類型 16
3.2.6. 標註類型 16
3.2.7. 複合類型 16
3.2.8. 中綴類型 17
3.2.9. 函數類型 18
3.2.10. 既存類型 18
3.2.11. Predef 中定義的原始類型 20
3.3. 非值類型 20
3.3.1. 方法類型 20
3.3.2. 多態方法類型 21
3.3.3. 類型構造器 21
3.4. 基本類型和成員定義 22
3.5. 類型間的關系 23
3.5.1. 類型恆等 23
3.5.2. 一致性 23
3.6. 易變類型 25
3.7. 類型擦除 25
4.1. 值聲明與定義 28
4.2. 變量聲明與定義 29
4.3. 類型聲明與類型別名 31
4.4. 類型參數 32
4.5. 差別標注 33
4.6. 函數聲明與定義 34
4.6.1. 叫名參數 35
4.6.2. 重複參數 35
4.7. Import 子句 37
5.1.1. 構造器調用 40
5.1.2. 類的線性化 41
5.1.3. 類成員 42
5.1.5. 繼承閉包 43
5.1.6. 前置定義 43
5.2. 修飾符 44
5.3. 類定義 46
5.3.1. 構造器定義 48
5.3.2. Case 類 49
5.4. 對象定義 51
6.1. 表達式類型化 54
6.2. 字面值 54
6.3. Null 值 54
6.4. 指示器 55
6.5. This 和 Super 55
6.6. 函數應用 56
6.7. 方法值 57
6.8. 類型應用 58
6.10. 實例創建表達式 58
6.11. 代碼塊 59
6.12. 前綴,中綴及後綴運算 60
6.12.1. 前綴運算 60
6.12.2. 後綴操做 60
6.12.3. 中綴操做 60
6.12.4. 賦值算符 61
6.13. 類型化的表達式 61
6.14. 標註表達式 62
6.16. 條件表達式 63
6.17. While 循環表達式 63
6.18. Do 循環表達式 64
6.19. For 語句段 64
6.20. Return 表達式 66
6.21. Throw 表達式 66
6.22. Try 表達式 66
6.23. 匿名函數 67
6.25. 隱式轉換 69
6.25.1. 值轉換 69
6.25.2. 方法轉換 69
6.25.3. 重載解析 69
6.25.4. 本地類型推斷 71
6.25.5. Eta 擴展 73
7.1. implicit 修飾符 75
7.2. 隱含參數 75
8.1.1. 變量模式 82
8.1.2. 類型化模式 82
8.1.3. 字面值模式 82
8.1.4. 穩定標識符模式 82
8.1.5. 構造器模式 83
8.1.6. 元組模式 83
8.1.7. 提取模式 83
8.1.8. 模式序列 84
8.1.9. 中綴操做符模式 84
8.1.10. 模式選擇 84
8.1.11. XML 模式 84
8.1.12. 正則表達式模式 84
8.1.13. 恆等模式 85
8.2. 類型模式 85
8.3. 模式中的類型參數推斷 85
8.4. 模式匹配表達式 87
8.5. 模式匹配匿名函數 88
9.1. 編譯單元 91
10.1. XML 表達式 93
10.2. XML 模式 94
12.2.1. 數字值類型 103
12.2.2. Boolean 類 105
12.2.3. Unit 類 106
12.3. 標準引用類 106
12.3.1. String 類 106
12.3.2. Tuple 類 106
12.3.3. Function 類 107
12.3.4. Array 類 107
12.4. Node 類 109
12.5. Predef 對象 111
前言
Scala 是一門類 Java 的編程語言,它結合了面向對象編程和函數式編程。Scala 是純面向對象的,每一個值都是一個對象,對象的類型和行爲由類定義,不一樣的類能夠經過混入(mixin)的方式組合在一塊兒。Scala 的設計目的是要和兩種主流面向對象編程語言Java 和 C#實現無縫互操做,這兩種主流語言都非純面向對象。
Scala 也是一門函數式變成語言,每一個函數都是一個值,原生支持嵌套函數定義和高階函數。Scala 也支持一種通用形式的模式匹配,模式匹配用來操做代數式類型,在不少函數式語言中都有實現。
Scala 被設計用來和 Java 無縫互操做(另外一個修改的 Scala 實現能夠工做在.NET 上)。Scala 類能夠調用 Java 方法,建立 Java 對象,繼承 Java 類和實現 Java 接口。這些都不須要額外的接口定義或者膠合代碼。
Scala 始於 2001 年,由洛桑聯邦理工學院(EPFL)的編程方法實驗室研發。2003 年11 月發佈 1.0 版本,本書描述的是 2006 年 3 月發佈的第二版,做爲語言定義和一些核心庫模塊的參考手冊,本書的目的不是教授 Scala 語言或它的概念,這些能夠參考其餘文檔[Oa04, Ode06, OZ05b, OCRZ03, OZ05a]。
Scala 是不少人共同努力的結果。1.0 版的設計和實現由 Philippe Altherr, Vincent Cremet, Gilles Dubo chet, Burak Emir, Stéphane Micheloud, Nikolay Mihaylov, Michel Schinz, Erik Sten man, Matthias Zenger 和本書做者完成; Iulian Dragos, Gilles Dubochet, Philipp Haller, Sean McDirmid, Lex Spoon 和 Geoffrey Washburn 加入了第二版語言和工具的研發。經過參與富有活力和靈感的討論,並對本書的舊版提出意見,Gilad Bracha, Craig
Cham bers, Erik Ernst, Matthias Felleisen, Shriram Krishnamurti, Gary Leavens, Sebastian Maneth, Erik Meijer, Klaus Ostermann, Didier Rémy, Mads Torgersen, 和 Philip Wadler 幫助造成了語言的設計。還有 Scala 郵件列表上的貢獻者,他們給予了很是有用的回饋,幫助咱們改進語言和工具。
Scala 程序使用的字符集是 Unicode 的基本多文種平面字符集; 目前不支持Unicode 中增補的字符。本章定義了 Scala 詞法的兩種模式:Scala 模式與 XML 模式。若是沒有特別說明,如下對 Scala 符號的描述均指 Scala 模式,常量字符„c‟指 ASCII 段\u0000-\u007F。
在 Scala 模式中,十六進制 Unicode 轉義字符會被對應的 Unicode 字符替換。
UnicodeEscape ::= \{\\}u{u} hexDigit hexDigit hexDigit hexDigit hexDigit ::= „0‟|...|„9‟|„A‟|...|„F‟|...|„a‟|...|„f‟|
符號由下面幾類字符構成(括號中是 Unicode 通用類別):
1. 空白字符。\u0020|\u0009| \u000D|\u000A
3. 數 字 „0‟|...|„9‟
4. 括號 „(‟ | „)‟ | „[‟ | „]‟ | „{‟ | „}‟。
5. 分隔符 „„‟ | „‟‟ | „」‟ | „.‟ | „;‟ | „,‟。
6. 算符字符。由全部的沒有包括在以上分類中的可打印 ASCII 字符\u0020-
\u007F,數學符號(Sm)以及其餘符號(So)構成
op ::= opchar {opchar}
varid ::= lower idrest plainid ::= upper idrest
| varid
| op
id ::= plainid
| „\‟‟ stringLit „\‟‟
idrest ::= {letter | digit}[„_‟ op]
有三種方法能夠構造一個標識符。第一,首字符是字母,後續字符是任意字母和數字。這種標識符還可後接下劃線‟_‟,而後是任意字母和數字。第二,首字符是算符字符,後 續字符是任意算符字符。這兩種形式是普通標識符。最後,標識符能夠是由反引號‟`‟括 起來的任意字符串(宿主系統可能會對字符串和合法性有些限制)。這種標識符能夠由除了 反引號的任意字符構成。
按慣例,標識符符合最長匹配原則。例如:
Big_bob++=‟def‟
能夠分解爲三個標識符 big_bob,++=,和 def。變量標識符(varid,小寫字母開頭)
和常量標識符(沒有小寫字母開頭的限制)的模式匹配規則有所不一樣。如下命名是保留字,不能用做詞法標識符的語法類 id。
abstract |
case |
|
catch |
|
class |
def |
do |
else |
|
extends |
|
false |
final |
finally |
for |
|
forSome |
|
if |
implicit |
import |
lazy |
|
match |
|
new |
null |
object |
override |
|
package |
|
private |
protected |
requires |
return |
|
sealed |
|
super |
this |
throw |
trait |
|
try |
|
true |
type |
val |
var |
|
while |
|
with |
yield |
_ : = |
=> <- |
<: |
<% >: |
# |
@ |
|
Unicode 算符\u21D2 „⇒‟ 和 \u2190 „←‟以及它們的 ASCII 對應‟=>‟也是保留字
示例 1.1.1 如下是一些標識符的示例
x + |
Object maxIndex `yield` αρετη |
_y |
p2p |
empty_? dot_product_* |
system |
_MAX_LEN_ |
|
|
|
示例 1.1.2 反引號括起來的字符串是那些 Scala 中是保留字的 Java 中標識符的一個方法。例如,在 Scala 中 Thread.yield()是非法的,由於 yield 是保留字。可是能夠這樣調用:
Thread.`yield`()
semi ::= „;‟ | nl{nl}
Scala 是一個基於行的語言。分號和換行都可做爲語句的結束。若是換行知足如下三個條件則會被認爲是一個特殊符號‟nl‟:
能夠做爲語句結束的符號是:常量,標識符,保留字以及如下的分隔符:
this |
null |
true |
false |
return |
type |
<xml-start> |
- |
) |
] |
} |
|
|
|
能夠做爲語句開始的符號是除了如下分隔符及保留字以外的全部 Scala 符號:
catchelseextends finally forSome matchrequires
withyield, . ; : _ = => <- <: <% >: # [ )
] }
符號 case 只有在 class 或者 object 符號以前才能夠做爲語句開始。多行語句許可的條件:
注意在 XML 中大括號{..}被轉義,字符串並非符號。所以當換行被容許時不要關閉區域。
通常地,即便連續的兩個非換行符號中有多行,也只會插入一個 nl 符號。然而,若是兩個符號被至少一個空行(行中沒有可打印字符)分隔開,那麼兩個符號中就會插入兩個nl 符號。
Scala 語法(全文見附錄 A)容許可選的 nl 符號,但分號不在此列。這樣在某些位置換行並不會結束一個表達式或語句。這些位置以下所列:
如下位置容許多個換行符號(換了分號是不行地):
- 在條件表達式(§6.16) 或 while 循環(§6.17)的條件及下一個表達式間
- For 循環(§6.19)中計數器及下一個表達式間
- 類型定義或聲明中,在開始的 type 關鍵字以後如下位置容許單個換行:
- 在一個是當前語句或表達式的合法繼續的大括號」{」前
- 若是下行的第一個符號是一個表達式的開始(§6.12),本行的中綴算符以後
- 在一個參數子句前(§4.6)
- 在一個標註(§11)以後
示例 1.2.1 如下是跨兩行的四個合法語句。兩行間的換行符號並未做爲語句結束。
if(x > 0) x = x – 1
while(x > 0) x = x / 2
for(x <- 1 to 10) println(x)
type
IntList = List[Int]
示例 1.2.2 如下代碼定義了一個匿名類
new Iterator[Int]
{
private var x = 0
def hasNext = true
def next = { x += 1; x }
}
加一個換行後,一樣的代碼就成了一個對象建立和一個局部代碼塊
new Iterator[Int]
{
private var x = 0
def hasNext = true
def next = { x += 1; x }
}
示例 1.2.3 如下代碼定義了一個表達式:
x < 0 || x > 10
加一個換行後就成了兩個表達式:
x < 0 ||
x > 10
示例 1.2.4 如下代碼定義了一個單一的柯里化的函數:
def func(x: Int)
(y: Int) = x + y
加一個換行後,一樣的代碼就成了一個抽象函數和一個非法語句
def func(x: Int)
(y: Int) = x + y
示例 1.2.5 如下代碼是一個加了標註的定義:
@serializable
protected class Data{...}
加一個換行後,一樣的代碼就成了一個屬性標記和一個單獨的語句(其實是非法的)
@serializable
protected class Data{...}
字面值包括整數,浮點數,字符,布爾值,記號,字符串。這些字面值的語法均和
Java 中的字面值一致。語法:
Literal ::= [„-„] integerLiteral
| [„-„] floatingPointLiteral
| booleanLiteral
| characterLiteral
| stringLiteral
| symbolLiteral
| „null‟
integerLiteral ::= (decimalNumeral | hexNumeral | octalNumeral)[„L‟|„l‟] decimalNumeral ::= „0‟ | nonZeroDigit {digit}
hexNumeral ::= „0‟„x‟ hexDigit {hexDigit} octalNumeral ::= „0‟ octalDigit {octalDigit} digit ::= „0‟ | nonZeroDigit nonZeroDigit ::= „1‟ | ... | „9‟
octalDigit ::= „0‟ | ... | „7‟
整型字面值一般表示 Int 型,或者後面加上 L 或 l 表示 Long 型。Int 的值的範圍是
-231 到 231-1 間的整數,包含邊界值。Long 的值的範圍是-263 到 263-1 間的整數,包含邊界值。整型字面值的值超出以上範圍就會致使編譯錯誤。
若是一個字面值在表達式中指望的類型 pt(§6.1)是 Byte, Short 或者 Char 中的一個,而且整數的值符合該類型的值的範圍,那麼這個數值就會被轉爲 pt 類型,這個字面值的類型也是 pt。數值範圍以下所示:
Byte -27 到 27-1
Short -215 到 215-1
Char 0 到 216-1
示例 1.3.1 如下是一些整型字面值:
0 21 0xFFFFFFFF 0777L
floatingPointLiteral ::= digit { digit } „.‟ { digit } [ exponentPart ]
[ floatType ]
| „.‟ digit { digit } exponentPart [ floatType ]
|digit{digit}exponentPart [ floatType]
| digit { digit } [ exponentPart ] floatType exponentPart ::= („E‟ | „e‟)[„+‟ | „-‟]digit{digit} floatType ::= „F‟ | „f‟ | „D‟ | „d‟
若是浮點數字面值的後綴是 F 或者 f,那麼這個字面值的類型是 Float,不然就是Double。Float 類型包括全部 IEEE 754 32 位單精度二進制浮點數值,Double 類型包括全部 IEEE 754 64 位雙精度二進制浮點數值。
若是程序中浮點數字面值後面跟一個字母開頭的符號,那麼這二者之間應當至少有一個空白字符。
示例 1.3.2 如下是一些浮點型字面值:
0.0 1e30f 3.14159f 1.0e-100 .1
示例 1.3.3 短語„1.toString‟將被解析爲三個符號:„1‟, „.‟和„toString‟。可是若是在句點後插入一個空格,短語„1. toString‟就會被解析爲一個浮點數„1.‟和一個標識符„toString‟。
booleanLiteral ::= „true‟ | „false‟
布爾型字面值 true 和 false 是 Boolean 類型的成員
characterLiteral ::= „\‟‟ printableChar „\‟‟
| „\‟‟ charEscapeSeq „\‟‟
字符型字面值就是單引號括起來的單個字符。字符能夠是可打印 unicode 字符或者由一個轉義序列(§1.3.6)描述的 unicode 字符。
示例 1.3.4 如下是一些字符型字面值:
„a‟ „\u0041‟ „\n‟ „\t‟
注意„\u000A‟不是一個合法的字符常數,由於在處理字面值前已經完成了 Unicode 轉換,而 Unicode 字符\u000A(換行)不是一個可打印字符。可使用轉義序列„\n‟或八進制轉義„\12‟來表示一個換行字符(§1.3.6)。
stringLiteral ::= „\」‟ {stringElement} „\」‟
stringElement ::= printableCharNoDoubleQuote | charEscapeSeq
字符串字面值是由雙引號括起來的字符序列。字符必須是可打印 unicode 字符或者轉義序列(§1.3.6)。若是一個字符串字面值包括雙引號,那麼這個雙引號必須用轉義字符,好比:\」。字符串字面值的值是類 String 的一個實例。
示例 1.3.5 如下是一些字符串字面值
「Hello,\nWorld!」
「This string contains a \」 character.」
stringLiteral ::= „」」」‟ multiLineChars ‟」」」‟ multiLineChars ::= {[„」‟][„」‟] charNoDoubleQuote}
多行字符串字面值是由三個雙引號括起來的字符序列」」」...」」」。字符序列是除了三個雙引號以外的任意字符序列。字符不必定必須是可打印的;換行或者其餘控制字符也是能夠的。Unicode 轉義序列也能夠,不過在(§1.3.6)中定義的轉義序列不會被解析。 示例 1.3.6 如下是一個多行字符串字面值:
」」」the present string spans three lines.」」」
以上語句會產生以下字符串:
the present string spans three lines.
Scala 庫裏包括一個工具方法 stripMargin,能夠用來去掉多行字符串行首的空格。表達式:
」」」the present string spans three lines.」」」.stripMargin
的值爲:
the present string spans three lines.
stripMargin 方法定義在類 scala.runtime.RichString。因爲有預約義的從String 到 RichString 的隱式轉換,所以這個方法能夠應用到全部的字符串。
在字符串或字符字面值中能夠有如下轉義序列:
\b \u0008:退格 BS
\t \u0090:水平製表符 HT
\n \u000a:換行 LF
\f \u000c: 格式進紙 FF
\r \u000d:回車 CR
\」 \u0022:雙引號 」
\‟ \u0027:單引號 ‟
\\ \u005c:反斜線 \
0 到 255 間的 Unicode 字符能夠用一個八進制轉義序列來表示,即反斜線‟\‟後跟最多三個八進制。
在字符或字符串中,反斜線和後面的字符序列不能構成一個合法的轉義序列將會致使編譯錯誤。
symbolLiteral ::= „‟‟ idrest
記號字面值‟x 是表達式 scala.Symbol(「x」)的簡寫形式。Symbol 是一個 case
類(§5.3.2),定義以下:
package scala
final case class Symbol private (name: String) {
override def toString: String = 「‟」 + name
}
Symbol 的伴隨實例的 apply 方法中緩存了一個到 Symbol 的弱引用,所以一樣的記號字面值是引用相等的。
符號可由空白字符或註釋分隔開。註釋有兩種格式: 單行註釋是由//開始直到行尾的字符序列
多行註釋是在/*和*/之間的字符序列。多行註釋能夠嵌套,可是必須合理的嵌套。所以像/* /* */這樣的註釋是非法的,由於有一個沒有結束的註釋。
爲了容許包含 XML 片斷字面值。當遇到左尖括號‟<‟時,在如下狀況下詞法分析就會從 Scala 模式切換到 XML 模式:‟<‟前必須是空白,左括號或者左大括號,‟<‟後必須跟一個 XML 命名。
(whitespace | „(‟ | „{‟ } „<‟ (XNameStart | „!‟ | „?‟)
XNameStart ::= „_‟ | BaseChar | Ideographic (和 W3C XML 同樣, 但沒有„:‟)
掃描器遇到如下條件之一時將會從 XML 模式切換到 Scala 模式:
l 由‟<‟開始的 XML 表達式或模式已被成功解析。
l 解析器遇到一個內嵌的 Scala 表達式或模式強制掃描器返回正常模式,直到Scala 表達式或模式被成功解析。在這種狀況下,因爲代碼和XML 片斷能夠嵌套, 解析器將會用一個堆棧來儲存嵌套的XML 和 Scala 表達式。
注意在 XML 模式中不會生成 Scala 的符號,註釋會解析爲文本。
示例 1.5.1 如下定義了一個值,包括一個 XML 字面值,內嵌了兩個 Scala 表達式
val b = <book>
<title>The Scala Language Specification</title>
<version>{scalaBook.version}</version>
<authors>{scalaBook.authors.mkList(」」,」, 「, 」」)}</authors>
</book>
在 Scala 中,命名用來表示類型,值,方法以及類,這些統稱爲實體。命名在局部定義與聲明(§4),繼承(§5.1.3),import 子句,package 子句中存在,這些能夠統稱爲綁定。
綁定有優先級,定義(局部或繼承)有最高的優先級,而後是顯式 import,而後是通配符 import,而後是包成員,是最低的優先級。
有兩種不一樣的命名空間,一個是類型(§3),一個是術語(§6)。一樣的命名能夠表示類型或術語,這要看命名應用所在的上下文。
綁定有一個域,在此域中用單一命名定義的實體能夠用一個簡單名稱來訪問。域能夠嵌套。內部域中的綁定將會遮蓋同一域中低優先級的綁定,或者外部域中低優先級或同優先級的綁定。
注意遮蓋只是偏序關係。在下面狀況中:
val x = 1;
{ import p.x; x}
x 的綁定並無互相遮蓋。所以第三行中對 x 的引用的含義將是不明確的。
對一個未限定的(類型或術語)標識符x 的引用在如下條件下能夠被單一綁定:
l 在同一命名空間中用命名x 定義一個實體做爲標識符
l 在此命名空間中遮蓋全部的其餘定義命名 x 的實體綁定
若是沒有這樣的綁定將會致使錯誤。若是 x 由一個 import 子句綁定,那麼簡單命名x 將等價於由 import 子句映射所限定的命名。若是 x 由一個定義或聲明綁定,那麼 x 將指代由該綁定引入的實體。在此狀況下,x 的類型便是引用的實體的類型。
示例 2.0.2 如下是包 P 和 Q 中兩個名爲 X 的對象的定義:
package P {
object X { val x = 1; val y = 2 }
}
package Q {
object X { val x = true; val y = 「」 }
}
如下程序示意了它們間不一樣的綁定及優先級。
package P { //‟X‟由 package 子句綁定
import Console._ //‟println‟由通配符 import 綁定
object A {
println(「L4: 「+X) //這裏的‟X‟指‟P.X‟
object B {
import Q._{ //‟X‟由通配符 import 綁定
println(「L7: 「+X) //這裏的‟X‟指‟Q.X‟
import X._ //‟x‟和‟y‟由通配符 import 綁定
println(「L8: 「+x) //這裏的‟x‟指‟Q.X.x‟
object C {
val x = 3 //‟x‟由局部定義綁定println(「L12: 「+x) //這裏的‟x‟指常數‟3‟
{ import Q.X._ //‟x‟和‟y‟由通配符 import 綁定
// println(「L14: 「+x) //這裏到‟x‟的引用指代不明確
import X.y //‟y‟由顯式 import 綁定
println(「L16: 「+y) //這裏的‟y‟指‟Q.X.y‟
{ val x = 「abc」 //‟x‟由局部定義綁定
import P.X._ //‟x‟和‟y‟由通配符 import 綁定
// println(「L19: 「+y) //這裏到‟y‟的引用指代不明確println(「L20: 「+x) //這裏的‟x‟指字符串」abc」
}}}}}}
一個到限定的(類型或術語)標識符 e.x 的引用指在同一個命名空間中 e 的類型 T 的一個名爲 x 的成員做爲標識符。若是 T 不是值類型(§3.2)將會致使錯誤。e.x 的類型就是引用的實體T 的成員的類型。
Type ::= InfixType „=>‟ Type
| „(‟[„=>‟ Type] „)‟ „=>‟ Type
| InfixType [ExistentialClause] ExistentialClause ::= „forSome‟ „{„ ExistentialDc
{ semi ExistentialDcl} „}‟ ExistentialDcl ::= „type‟ TypeDcl
| „val‟ ValDcl
InfixType ::= CompoundType {id [nl] CompoundType} CompoundType ::= AnnotType {„with‟ AnnotType}[Refinement]
| Refinement
AnnotType ::= SimpleType {Annotation}
SimpleType ::= SimpleType TypeArgs
| SimpleType „#‟ id
| StableId
| Path „.‟ „type‟
| „(„ Types [„,‟] „)‟
TypeArgs ::= „[„ Types „]‟
Types ::= Type {„,‟ Type}
一階類型和類型構造器(用類型的參數構造類型)是有區別的。一階類型的一個子集是
值類型,表示(一階)值的集合。值類型能夠是具體的或者抽象的。
每一個具體的值類型能夠用一個類類型來表示,好比指向某類1(§5.3) 的類型指示器(§3.2.3),或者表示類型交集(可能會加一個修飾(§3.2.7)來限制其成員的類型)的複合類型(§3.2.7)。類型參數(§4.4)和抽象類型綁定(§4.3)引入了抽象值類型。類型中的括號用來建組。
非值類型描述了那些不是值(§3.3) 的標識符的屬性。例如, 一個類型構造器(§3.3.3)並不指明值的類型。然而,當一個類型構造器應用到正確的類型參數上時,就會產生一個多是值類型的一階類型。
在 Scala 中,非值類型被間接表述。例:寫下一個方法簽名來描述一個方法類型,雖然經過它能夠獲得對應的函數類型(§3.3.1),可是它自己並非一個真正的類型。類型構造器是另一個例子,好比咱們能夠寫 type Swap[m[_,_],a,b] = m[b,a],可是並無定義直接給出對應的匿名類型函數的語法。
1 咱們假定對象和包都隱式地定義一個類(與對象或包同名,可是在用戶程序中不可訪問)
Path ::= StableId
| [id „.‟] this
StableId ::= id
| Path „.‟ id
| [id „.‟] „super‟ [ClassQualifier] „.‟ id ClassQualifier ::= „[„ id „]‟
路徑不是類型自己,可是它們能夠是命名類型的一部分,這個功能是 Scala 類型系統的一個核心角色。
一個路徑是下面定義中的一個:
l 空路徑 ε(不能在程序中顯式地寫出來)
l C.this,C 是一個類的引用。當在C 引用範圍內時,路徑 this 是 C.this 的簡寫。
l p.x,p 是一個路徑,x 是 p 的一個穩定成員。穩定成員是由對象定義或者穩定類型的值定義引入的包或者成員(§3.6)。
l C.super.x 或 C.super[M].x,C 是一個類的引用,x 是 C 的超類或指定的父類 M 的穩定成員的引用。前綴 super 是 C.super 的簡寫,C 是包含引用範圍的類的名稱。
一個穩定的標識符是由標識符結束的一個路徑。
Scala 中的每一個值都有一個如下格式的類型。
SimpleType ::= Path „.‟ type
單例類型具備 p.type 的形式,p 是一個路徑,指向一個指望與 scala.AnyRef 一致(§6.1)的值。類型指一組爲 null 的或由 p 表示的值。
一個穩定類型指要麼是一個單例類型,要麼是特徵 scala.Singleton 的子類型。
SimpleType ::= SimpleType „#‟ id
類型映射 T#x 指類型 T 的類型成員 x。若是 x 指向抽象類型成員,那麼 T 必須是一個穩定類型(§3.2.1)。
SimpleType ::= StableId
類型指示指一個命名值類型。它能夠是簡單的或限定的。全部的這些類型指示都是類型映射的簡寫。
綁定在某類,對象或包 C 上的非限定的類型名 t 是 C.this.type#t 的簡寫,除非類型 t 是類型模式(§8.1.2)的一部分。後者中的 t 是 C#t 的簡寫。若是 t 沒有被綁定在某類,對象或包上,那麼t 就是ε.type#t 的簡寫。
一個限定的類型只是具備 p.t 的形式,p 是一個路徑(§3.1),t 是一個類型名。這個類型指示等價於類型映射 p.type#t。
示例 3.2.1 如下是一些類型指示以及擴展。咱們假定一個本地類型參數 t,一個值maintable 具備一個類型成員 Node,以及一個標準類 scala.Int
t ε.type#t
Int scala.type#Int
scala.Int scala.type#Int data.maintable.Node data.maintable.type#Node
SimpleType ::= SimpleType TypeArgs TypeArgs ::= „[‟ Types „]‟
參數化類型 T[U1,...,Un]包括類型指示 T 以及類型參數 U1,...,Un,n >=1。T 必須指向一個具備個參數類型 a1,...,an 的參數構造方法。
類型參數具備下界 L1,...,Ln 和上界 U1,...,Un。參數化類型必須保證每一個參數與其邊界一致:σLi<:Ti<:σUi,這裏 σ 表示[a1:=T1,...,an:=Tn]。
示例 3.2.2 如下是一些類型定義(部分):
class TreeMap[A <: Comparable[A], B]{ ... }
class List[A] { ... }
class I extends Comparable[I] { ... }
如下是正確的參數化類型:
TreeMap[I, String] List[I] List[List[Boolean]]
示例 3.2.3 使用示例 3.2.2 中的類型定義,如下是錯誤的參數化類型:
TreeMap[I] //錯誤的參數個數TreeMap[List[I], Boolean] //類型參數越界
SimpleType ::= „(‟ Types [„,‟] „)‟
元組類型(T1,...,Tn)是類 scala.Tuplen[T1,...,Tn](n>=2)的別名形式。此類型能夠在結尾處有個額外的逗號,例:(T1,...,Tn,)。
元組類是 case 類,其字段能夠用選擇器_1,...,_n 來訪問。在對應的 Product 特徵中有他們的抽象函數。這些元組類以及 Product 特徵都是標準 Scala 類庫的一部分, 其形式以下:
case class Tuplen[+T1,...,+Tn](_1: T1,...,_n: Tn) extends
Productn[T1,...,Tn]{}
trait Productn[+T1,...,+Tn]{
override def arity = n
def _1: T1
...
def _n: Tn
}
AnnotType ::= SimpleType {Annotation}
標註類型 T a1,...,an 就是給類型T 加上標註 a1,...,an.
CompoundType ::= AnnotType {„with‟ AnnotType} [Refinement]
| Refinement
Refinement ::= [nl] „{‟ RefineStat {semi RefineStat} „}‟ RefineStat ::= Dcl
| „type‟ TypeDef
|
複合類型 T1 with ... with Tn {R}指一個擁有 T1,...,Tn 類型中的成員以及修飾{R}的對象。若是對象中有聲明或定義覆蓋了成分類型 T1,...,Tn 中的聲明或定義,就會應用一般的覆蓋規則(§5.1.4);不然這個聲明或定義就將是所謂的「結構化的」2。在一個結構化修飾的方法聲明中,任何值參數的類型只是指修飾內部包含的類型參數或抽象類型。也就是它必須指代一個函數自己的類型參數,或者在修飾內部的一個類型定義。該
2 到一個結構化定義的成員的引用(方法調用或訪問值或變量)可能產生比等價的非結構化成員慢的多的代碼。
限制並不對函數的返回類型起做用。
若是沒有修飾,那麼默認就會添加一個空的修飾,例:T1 with ... with Tn 便是
T1 with ... with Tn {}的簡寫。
一個複合類型能夠只有修飾{R} 而沒有前面的成分類型。這樣的類型等價於
AnyRef{R}。
示例 3.2.4 如下是如何聲明以及使用參數類型包含結構化聲明修飾的函數。
case class Bird (val name: String) extends Object {
def fly(height: Int) = ...
...
}
case class Plane (val callsign: String) extends Object {
def fly(height: Int) = ...
...
}
def takeoff(
runway: Int,
r: { val callsign: String; def fly(height: Int) }) = { tower.print(r.callsign + 「 requests take-off on runway 「 + runway) tower.read(r.callsign + 「 is clear for take-off」)
r.fly(1000)
}
val bird = new Bird(「Polly the parrot」){ val callsign = name }
val a380 = new Plane(「TZ-987」) takeoff(42, bird)
takeoff(89, a380)
雖然 Bird 和 Plane 沒有除了 Object 以外的任何父類,用結構化聲明修飾的函數
takeoff 的參數r 卻能夠接受任何聲明瞭值 callsign 以及函數 fly 的對象。
InfixType ::= CompoundType {id [nl] CompoundType}
中綴類型 T1 op T2 由一箇中綴算符 op 應用到兩個操做數 T1 和 T2 上得來。這個類型等價於類型應用 op[T1, T2]。中綴算符能夠是除*以外的任意的標識符,由於*被保留做爲重複參數類型的後綴(§4.6.2)。
全部類型的中綴算符擁有一樣的優先級;所以必須用括號來改變順序。類型算符的結合性(§6.12)由其形式來決定:由‟:‟結尾的類型算符是右結合的,其餘的是左結合的。
在 一個連續 的類型中綴 運算 t0 op1 t1 op2...opn tn 裏, 全部的 算符op1,...,opn 必須具備相同的結合性。若是都是左結合的,該序列就被解析爲(...(t0 op1 t1) op2...) opn tn,不然會被解析爲t0 op1 (t1 op2 (...opn tn)..)。
Type ::= InfixType „=>‟ Type
| „(‟ [„=>‟ Type] „)‟ „=>‟ Type
類型 (T1,...,Tn) => U 表示那些參數類型爲 T1,...,Tn,併產生一個類型爲 U 的結果的函數。若是隻有一個參數類型則(T)=>U 能夠簡寫爲 T=>U。類型(=>T)=>U 表示以類型爲 T 的傳名(§4.6.1)參數併產生類型爲 U 的結果。函數類型是右結合的,例: S=>T=>U 等價於 S=>(T=>U)。
函數類型是定義了 apply 函數的類類型的簡寫。好比 n 型函數類型(T1,...,Tn) => U 就是類 Functionn[T1,...,Tn,U]的簡寫。Scala 庫中定義了 n 爲 0 至 9 的這些類類型,以下所示:
package scala
trait Functionn[-T1,...,-Tn, +R] {
def apply(x1: T1,...,xn: Tn): R
override def toString = 「<function>」
}
所以,函數類型與結果類型是協變(§4.5)的,與參數類型是逆變的。
傳名函數類型(=>T)=>U 是類類型 ByNameFunction[T,U]的簡寫形式,定義以下:
package scala
trait ByNameFunction[-T, +R] {
def apply(x: => T): R
override def toString = 「<function>」
}
Type ::= InfixType ExistentialClauses ExistentialClauses ::= „forSome‟ „{‟ ExistentialDcl
{semi ExistentialDcl} „}‟ ExistentialDcl ::= „type‟ TypeDcl
| „val‟ ValDcl
既存類型具備 T forSome {Q} 的形式, Q 是一個類型聲明的序列(§4.3) 。設t1[tps1]>:L1<:U1,...,tn[tpsn]>:Ln<:Un 是 Q 中聲明的類型(任何類型參數部分[tpsi]均可以沒有)。每一個類型 ti 的域都包含類型 T 和既存子句 Q。類型變量 ti 就稱爲在類型 T forSome {Q}中被綁定。在 T 中可是沒被綁定的類型變量就被稱爲在 T 中是自由的。
T forSome {Q}的類的實例就是類 σT,σ 是 t1,...,tn 上的迭代,對於每個 i, 都有 σLi<:σti<:σUi。既存類型 T forSome{Q}的值的集合就是全部其類型實例值的集
合的合集。
T forSome {Q}的斯科倫化是一個類實例 σT,σ 是[t‟1/t1,..., t‟n/tn 上的迭代, 每一個t‟i 是介於 σLi 和 σUi 間的新的抽象類型。
簡化規則
既存類型遵循如下四個等效原則:
在值上的既存量化
爲了語法上的方便,在既存類型上的綁定子句能夠包括值聲明 val x: T。既存類型T forSome { Q; val x: S; Q‟} 是 T‟ forSome { Q; type t <: S with Singleton; Q‟}的簡寫形式,此處 t 是一個新的類型名,T‟是將 T 中全部 x.type 用t 代替的結果。
WildcardType ::= „_‟ TypeBounds
Scala 支持既存類型的佔位符語法。通配符類型的形式爲 _>:L<:U。兩個邊界都可忽略。若是下界>:L 被忽略則用>:scala.Nothing 。若是上界<:U 被忽略則用
<:scala.Any。通配符類型是既存限定類型變量的簡寫,既存的限定條件是內涵的。
通配符類型只能做爲參數化類型的類型參量出現。設 T=p.c[targs,T,tags‟]是一個參數化類型,targs,targs‟能夠爲空,T 是一個通配符類型_>:L<:U。那麼 T 等價於如下既存類型:
p.c[tags,t,tags‟] forSome { type t >:L<:U}
t 是一個新的類型變量。通配符類型能夠做爲中綴類型(§3.2.8) , 函數類型(§3.2.9)或元組類型(§3.2.5)的一部分出現。它們的擴展也就是等價參數化類型的擴展
示例 3.2.5 假定如下類定義:
class Ref[T]
abstract class Outer { type T }
如下是一些既存類型的例子:
Ref[T] forSome { type T <: java.lang.Number } Ref[x.T] forSome { val x: Outer }
Ref[x_type # T] forSome { type x_type <: Outer with Singleton }
列表中的後兩個類型是等價的。使用通配符語法的另外一種形式是:
Ref[_ <: java.lang.Number]
Ref[(_ <: Outer with Singleton)# T]
示例 3.2.6 類型 List[List[_]]等價於既存類型:
List[List[t] forSome { type t }]
示例 3.2.7 假定有協變類型:
class List[+T]
類型:
List[T] forSome { type T <: java.lang.Number }
應用上面的第四條簡化規則,上式等價於:
List[java.lang.Number] forSome { type T <: java.lang.Number }
若是再應用上面的第二和第三條簡化規則,上式可化爲:
List[java.lang.Number]
每一個 Scala 程序都默認 import 一個 Predef 對象。該對象定義了一些原始類型作爲類類型的別名。數值類型和布爾型有標準的 Scala 類。String 類型與宿主系統的String 類型一致。在 Java 環境下,Predef 包括如下類型綁定:
type byte = scala.Byte type short = scala.Short type char = scala.Char
type int = scala.Int
type long = scala.Long type float = scala.Float type double = scala.Double type Boolean = scala.Boolean
type String = java.lang.String
如下類型並不表示值的集合,也並不顯式地出如今程序中。它們只以已定義標識符的內部類型而引入。
方法類型在內部表示爲(Ts)U,(Ts)是一個類型序列(T1,...,Tn) n>=0,U 是一個(值或者方法)類型。這個類型表示一個命名的方法,其參數的類型是 T1,...,Tn,返回結果的類型是U。
方法類型是右結合的,(Ts1)(Ts2)U 被處理的方式是(Ts1)((Ts2)U)。
一個特例是沒有參數的方法類型。能夠寫爲=>T 的形式。無參數方法名稱表達式將會在每次名稱被引用時求值。
方法類型並不以值的類型的形式存在。若是方法名以值的方式被引用,其類型將會被自動轉換爲對應的函數類型(§6.25)。
示例 3.3.1 如下聲明:
def a: Int
def b (x: Int): Boolean
def c (x: Int)(y: String, z: String): String
產生如下類型:
a: => Int
b: (Int) Boolean
c: (Int)(String, String) String
多態方法類型在內部表示爲[tps]T , [tps] 是類型參數部分[a1 >: L1 <: U1,...,an >: Ln <: Un],n>=0, T 是一個(值或方法)類型。該類型表示一個以S1,...,Sn 爲類型參量併產生類型爲 T 的結果的命名方法,參數類型 S1,...,Sn 與下界L1,...,Ln 和上界U1,...,Un 一致(§3.2.4)。
示例 3.3.2 如下聲明:
def empty[A]: List[A]
def union[A <: Comparable[A]] (x: Set[A], xs: Set[A]): Set[A]
產生以下類型:
empty: [A >: Nothing <: Any] List[A]
union: [A >: Nothing <: Comparable[A]] (x: Set[A], xs: Set[A]) Set[A]
類型構造器在內部的表示方法相似於多態方法類型。[+/- a1 >: L1 <: U1,...,+/-an >: Ln <: Un] T 表示一個指望是類型構造器參數(§4.4)或有對應類型參數子句的抽象類型構造器綁定(§4.3)的類型。
示例 3.3.3 如下是類 Iterable[+X]的片斷:
trait Iterable[+X] {
def flatMap[newType[+X]<:Iterabe[X], S](f: X => newType[S]): newType[S]
}
從概念上來說,類型構造器 Iterable 是匿名類型[+X] Iterable[X]的名稱,在
flatMap 中傳遞給 newType 類型構造器參數。
類成員的類型取決於成員被引用的方式。主要有三個概念:
l C 是類型 C 以及其父類型 T1,...,Tn 的基本類型,同時也是組合類型 T1
with ... with Tn {R}的基本類型。
l 類型別名的基本類型是別名的類型的基本類型
l 抽象類型的基本類型是其上界的基本類型
l 參數化類型 C[T1,...,Tn]的基本類型是類型 C 的基本類型,C 的每個類型參數 ai 被對應的參數類型Ti 代替
l 單例類型p.type 的基本類型是類型 p 的基本類型
l 複合類型 T1 with ... with Tn {R}的基本類型是全部 Ti 的基本類型的縮減合併。意思是設合集 Φ 爲 Ti 的基本類型的合集,若是 Φ 包括同一個類的多個類型實例,好比 Si#C[Ti1,...,Tin](i∈I),那麼全部的這些實例將會被一個與其餘一致的實例代替。若是沒有這樣一個實例存在就會致使錯誤。若是存在這樣一個縮減合併,那麼該集合會產生類類型的集合,不一樣的類型是不一樣類的實例。
l 類型選擇 S#T 的基本類型以下肯定:若是 T 是一個抽象類或別名,那麼前面的子句就會被應用。不然 T 必須是一個定義在某個類 B 中的(可能仍是參數化的)類類型。那麼 S#T 的基本類型就是從前綴類型 S 中看到的 B 中 T 的基本類型。
l 既存類型 T forSome {Q}的基本類型是全部 S forSome {Q}類型,S 是
T 的基本類型
l 若是S = ε.type,那麼從 S 看到的 C 中的 T 就是T 自己
l 不然,若是 S 是既存類型 S‟ forSome {Q},從 S‟看 C 中的 T 將會是 T‟, 那麼從 S 看 T 中的 C 將會是 T‟ forSome {Q}
l 不然,若是 T 是某類 D 的第 i 個類型參數,那麼
n 若是 S 有基本類型 D[U1,...,Un],[U1,...,Un]是類型參數,那麼從S 中看到的 C 中的T 就是Ui
n 不然,若是 C 定義在類 C‟中,那麼從 S 中看到的 C 中的 T 與在 S‟中看到的C‟中的T 是同樣的
n 不然,若是 C 不是定義在其餘類中,那麼從 S 中看到的 C 中的 T 就是 T
自己
l 不然,若是 T 是某類 D 的單例類型D.this.type,那麼
n 若是 D 是 C 的子類,S 的基本類型中有一個類 D 的類型實例,那麼從 S
中看到的C 中的 T 就是 S
n 不然,若是 C 定義在類 C‟中,那麼從 S 中看到的 C 中的 T 與 S‟中看到的 C‟中的 T 相同
n 不然,若是 C 不是定義在其餘類中,那麼從 S 中看到的 C 中的 T 就是 T
自己
l 若是T 是其餘類型,那麼將在全部其類型組件中執行以上描述的映射
若是 T 是一個可能參數化的類類型,T 的類定義在某個類 D 中,S 是某前綴類型,那麼「從 S 中看到T」就是「從S 中看到D 中的T 的簡寫」。
類型映射 S#t 的定義就是 S 中類型 t 的成員綁定 dt。在此狀況下,咱們能夠說S#t 由dt 定義。
類型間有兩種關係:
類型恆等 T ≡ U T 和U 能夠在任何狀況下互相替換一致 T <: U 類型T 與類型U 一致
類型間的恆等(≡)關係是最小的一致性3,具備如下特色:
l 若是t 是一個類型別名的定義 type t = T,那麼 t 就等價於 T
l 若是路徑p 有一個單例類型 q.type 那麼 p.type ≡ q.type
l 若是 O 是一個對象的定義,p 是一個路徑,僅包括包或者對象的選擇器,並以 O
結束。那麼 O.this.type ≡ p.type
l 兩個複合類型(§3.2.7)相等的條件是:他們組件序列元素相等,而且順序一致, 並且修飾也相等。若是兩個修飾綁定到一樣的命名,而且兩個修飾的每一個聲明的 實體的修飾符,類型和邊界也相等,那麼這兩個修飾相等。
l 兩個方法類型(§3.3.1)相等的條件是:結果類型相等,參數數目一致,對應的參數類型一致。參數名稱沒必要相等。
l 兩個多態方法類型(§3.3.2)相等的條件是:一樣數目的類型參數,若是將另外一 組類型參數重命名爲當前一組,獲得的類型以及對應的參數類型的上下界也相等。
l 兩個既存類型(§3.2.10)相等的條件是:一樣數目的量詞,若是用另外一組類型量詞替換當前一組,定量類型以及對應的量詞的上下界也相等。
l 兩個類型構造器(§3.3.3)相等的條件是:一樣數目的類型參數,若是用另外一組類型參數替換當前一組,結果類型以及變化,對應的類型參數的上界和下界也是相等的。
一致性關係(<:)是符合如下條件的最小的可傳遞關係:
l 一致性包含相等。若是T ≡ U 那麼T <: U
3 一致性是上下文構成中封閉的等價關係
l 對於任意的值類型T,有 scala.Nothing <: T <: scala.Any
l 對於任意的類型構造器 T(任意數目的類型參數),有 scala.Nothing <: T <: scala.Any
l 對 於 任 意 的 類 類 型 T , T <: scala.AnyRef , 並 且 不 是 T <: scala.NotNull,那麼有 scala.Null <: T
l 類型變量或抽象類型 t 與其上界一致,其下界與t 一致
l 類類型或者參數化類型與其任何基本類型一致
l 單例類型p.type 與路徑p 的類型一致
l 單例類型p.type 與類型 scala.Singleton 一致
l 類型映射 T#t 與 U#t 一致,若是T 與 U 一致的話
l 參數化類型 T[T1,...,Tn]與 T[U1,...,Un]一致的條件是(i=1,...,n):
n 若是T 的第 i 個類型參數聲明爲協變量,那麼 Ti <: Ui
n 若是T 的第 i 個類型參數聲明爲逆變量,那麼 Ui <: Ti
n 若是T 的第 i 個類型參數既不是協變量也不是逆變量,那麼Ui≡Ti
l 複合類型T1 with ... with Tn {R}與其每一個組件類型 Ti 一致
l 若是 T <: Ui(i=1,...,n),對 R 中任一類型或值 x 的綁定 d,T 中存在一個包括 d 的
x 的成員綁定,那麼 T 與複合類型 U1 with ... with Un {R}一致
l 若是既存類型 T forSome {Q} 的斯科倫化(§3.2.10)與 U 一致,那麼該類型與 U
一致
l 若是 T 與 U forSome {Q}的某一個類型實例(§3.2.10)一致,那麼 T 與既存類型U forSome {Q}一致
l 若是 Ti≡T‟i(i=1,...,n),U 與 U‟一致,那麼方法類型(T1,...,Tn)U 與
(T‟1,...,T‟n)U‟一致。
l 多態類型[a1 >: L1 <: U1,...,an>: Ln<: Un]T 與[a1 >: L‟1 <:
U‟1, ... ,an >: L‟n <: U‟n ] T‟一致的條件是:假設 L‟1 <: a1 <:
U‟1, ... ,L‟n <: an <: U‟n , T :< T‟, Li <: L‟i U‟I <: Ui, i=1,...,n.
l 類型構造器 T 和 T‟ 有相似的規則。能夠用類型參數子句[a1,...,an] 和[a‟1,...,a‟n]來區分 T 和 T‟。每一個 ai 和 a‟i 可能包括差別標註,高階類型參數子句和邊界。那麼,T 與 T‟一致的條件就是,任意列表[t1,...,tn](包括聲明的差別,邊界和高階類型參數子句)是 T‟的有效的類型參數,同時也是 T 的有效的類型參數,而且有 T[t1,...,tn] <: T‟[t1,...,tn]。這將致使:
n ai 的邊界必需要比對應的a‟i 的邊界要弱。
n ai 的差別必需要與 a‟i 的差別一致,協變與協變一致,逆變與逆變一致,任意差別與無差別一致。
n 這些限制要遞歸應用到ai 和 a‟i 對應的高階類型參數子句上.
在如下某個條件下,類類型 C 的複合類型的聲明或定義將包括另一個類類型 C‟
的符合類型的同名聲明:
l 若是 T<:T‟,一個值聲明或定義定義了一個類型爲 T 的命名 x,包括一個值或者方法聲明定義了類型爲T‟的 x。
l 若是 T<:T‟,一個方法聲明或定義定義了一個類型爲 T 的命名 x,包括一個方法聲明定義了類型爲T‟的 x
l 若是 T≡T‟,類型別名 type t[T1,...,Tn] = T 包括一個類型別名 type
t[T1,...,Tn] = T‟。
l 若是 L‟ <: L 且 U <: U‟,類型聲明 type t[T1,...,Tn] >: L <: U 包括一個類型聲明 type t[T1,...,Tn] >: L‟ <: U‟。
l 若是 L <: t <: U 一個綁定到一個類型名稱 t 的類型或類定義包括一個抽象聲明 type t[T1,...,Tn] >: L <: U。
<:關係在類型間組成了一種順序,好比傳遞和自反。一個類型集合的最小上界與最大下界能夠理解爲與該順序有關。
注意:類型集合的最小上界和最小下界不必定老是存在。例如,如下類定義:
class A[+T] {}
class B extends A[B]
class C extends A[C]
類型 A[Any],A[A[Any]],A[A[A[Any]]],...構成了一個 B 和 C 上界的降序序列。最小上界將會是這個序列的極限,這個極限不可能做爲一個 Scala 類型存在。因爲這種狀況一般不大可能探測到,所以若是指定了一個最小上界或最大下界的類型,而且這個邊界可能複雜度超過編譯器設定的限制4的話就可能被 Scala 編譯器拒絕。
最小上界和最大下界也可能不惟一。例如 A with B 和 B with A 都是 A 和 B 的最大下界。若是有多個最小上界或最大下界,Scala 編譯器可能會自動選取其中某一個。
類型易變大體的意思是說一個類型參數或抽象類型實例沒有非 null 值。在(§3.1)的解釋中,易變類型的值成員不能出如今路徑中。
易變類型符合下面 4 個分類中的一個:
複合類型T1 with ... with Tn {R}是易變的條件是如下三個中的一個.
在這裏類型 S 給類型 T 提供了一個抽象成員的意思是 S 有一個抽象成員同時也是 T 的成員。一個修飾 R 類型 T 提供一個抽象成員的意思是 R 包括一個抽象成員同時也是 T 的成員。
類型設計器是易變的意思是它是一個易變類型的別名,或者它制定了一個以易變類型爲其上界的類型參數或抽象類型。
若是路徑p 是易變的,那麼單例類型 p.type 就是易變的。既存類型T forSome {Q}是易變的條件是 T 是易變的。
類型是通用的的含義是其包括類型參數或類型變量。類型擦除指的就是從(通用)類型到特定類型的映射。類型T 的擦除類型的寫法是|T|。擦除映射的定義以下。
l 別名類型的擦除就是其右側的擦除
l 抽象類型的擦除就是其上界的擦除
4 現有的 Scala 編譯器限定該邊界的嵌套的參數化層次最多隻能比操做數類型的最大嵌套層次深兩
層
l 參數化類型 scala.Array[T1]的擦除是 scala.Array[|T1|]
l 其餘參數化類型 T[T1,...,Tn]的擦除就|T|
l 單例類型p.type 的擦除就是p 的類型的擦除
l 類型映射 T#x 的擦除就是|T|#x
l 複合類型T1 with ...with Tn {R}的擦除就是|T1|
l 既存類型T forSome {Q}的擦除就是|T|
Dcl ::= „val‟ ValDcl
| „var‟ VarDcl
| „def‟ FunDcl
| „type‟ {nl} TypeDcl
PatVarDef ::= „val‟ PatDef
| „var‟ VarDef
Def ::= PatVarDef
| „def‟ FunDef
| „type‟ {nl} TypeDef
| TmplDef
聲明引入命名並給其指定類型。聲明能夠是類定義(§5.1)或者複合類型(§3.2.7)中修飾定義的一部分。
定義引入命名用以表示術語或類型。定義能夠是對象或類定義的一部分,或者只是侷限在一個代碼塊中。聲明和定義都會產生關聯類型命名和類型定義與邊界的綁定,將術語名稱和類型聯繫起來。
聲明或定義引入的命名的範圍是包括該綁定的整個語句序列。。然而在代碼塊的前向引用中有一個限制:語句序列 s1...sn 構成一個代碼塊,若是 si 中的一個簡單命名引用一個定義在 sj 中的實體,且 j>=i,那麼 si 和 sj 之間(包括這二者)的定義不能是值或者變量定義。
Dcl ::= „val‟ ValDcl ValDcl ::= ids „:‟ Type Def ::= „val‟ PatDef
PatDef ::= Pattern2 {„,‟ Pattern2} [„:‟ Type] „=‟ Expr ids ::= id {„,‟ id}
值聲明 val x: T 表示 x 是一個類型爲T 的值的命名。
值定義 val x: T = e 表示 x 是表達式 e 求值的結果。若是值的定義不是遞歸的, 類型 T 能夠忽略,默認就是表達式 e 的打包類型(§6.1)。若是給出了類型 T,那麼 e 就被指望與其一致。
值定義的求值就是其右側表達式 e 的求值,除非有一個 lazy 修飾符。值定義的效果是將 x 綁定到變爲類型 T 的 e 的結果。lazy 型的值定義只在值第一次被訪問時纔對右側表達式求值。
值定義能夠在左側有一個模式(§8.1)。若是 p 是除了簡單命名或命名後跟冒號與類型的模式,那麼值定義 val p = e 可擴展爲如下形式:
...
val xn = $x._n
這裏的$x 是一個新命名。
val x = e match { case p => x }
e match { case p => ()}
示例 4.1.1 如下是一些值定義的例子:
val pi = 3.1414
val pi: Double = 3.1415 //與第一個等價
val Some(x) = f() //模式定義
val x :: xs = mylist //中綴模式定義
後兩個定義具備如下擴展:
val x = f() match { case Some(x) = x }
val x$ = mylist match { case x :: xs => {x, xs} }
val x = x$._1
val xs = x$._2
聲明或定義的值的命名不能以_=結束。
值聲明 val x1,...,xn: T 是 val x1: T; ...; val xn: T 的簡寫形式。值定義 val p1,...,pn = e 是 val p1 = e; ...; val pn = e 的簡寫形式。值定義val p1,...,pn: T = e 是 val p1:T = e; ...; val pn:T = e;的簡寫形式。
Dcl ::= „var‟ VarDcl
Def ::= „var‟ VarDef VarDcl ::= ids „:‟ Type VarDef ::= PatDef
| ids „:‟ Type „=‟ „_‟
變量聲明 var x: T 等價於聲明一個 getter 函數 x 和一個 setter 函數 x_=,以下所示:
def x: T
def x_= (y: T): Unit
包括變量聲明的類的實現能夠用變量定義來定義這些變量,也能夠直接定義 getter
和 setter 函數。
變量定義 var x: T = e 引入了一個類型爲 T 的可變變量,並用表達式 e 做爲初始值。類型 T 可忽略,默認用 e 的類型。若是給定了 T,那麼 e 被指望具備一致的類型(§6.1)。
變量定義能夠在左側有一個模式(§8.1)。若是 p 是除了簡單命名或命名後跟冒號與類型的模式,那麼變量定義 var p = e 具備和值定義一致的擴展模式,除了那些 p 中自由的命名是可變的變量,不是值。
任何聲明或定義的變量的命名不能以_=結尾。
變量定義 var x: T = _只能以模板成員出現。該定義表示一個可變字段和一個默認初始值。該默認值取決於類型T:
0 若是T 是 Int 或其子類型
0L 若是T 是 Long 0.0f 若是T 是 Float 0.0d 若是T 是 Double false若是T 是 Boolean
{} 若是T 是 Unit
null 全部其餘類型
若是變量定義是模板成員,那麼他們同時引入一個 getter 函數 x(返回當前賦給變量的值)和一個 setter 函數 x_=(改變當前賦給變量的值)。函數具備與變量聲明相同的識別標識。模板具備 getter 和 setter 函數成員,初始的變量不能以模板成員的形式被直接訪問。
示例 4.2.1 下面的例子顯示了 Scala 中如何模擬屬性。 首先定義了一個類TimeOfDayVar,具備可更新的整型字段表示時分秒。其實現包括一些測試,只容許合法值賦給這些字段。可是用戶代碼能夠像訪問其餘變量那樣直接訪問這些字段。
class TimeOfDayVar { private var h: Int = 0 private var m: Int = 0 private var s: Int = 0
def |
hours |
= |
h |
def |
hours_= (h: Int) |
= |
if (0 <= h && h < 24) this.h = h |
|
|
|
else throw new DataError() |
def |
minutes |
= |
m |
def |
minutes_= (m: Int) |
= |
if(0<= m && m < 60) this.m = m |
|
|
|
else throw new DataError() |
def |
seconds |
= |
s |
def |
seconds_= (s: Int) |
= |
if (0 <= s && s < 60) this.s = s |
|
|
|
else throw new DataError() |
} |
|
|
|
val d |
= new TimeOfDayVar |
|
|
d.hours = 8; d.minutes = 30; d.seconds = 0
d.hours = 24 //拋出一個 DataError 異常。
變量聲明 var x1,...,xn: T 是 var x1: T; ...; var xn: T 的簡寫形式。變量定義 var x1,...,xn = e 是 var x1 = e; ...; var xn = e 的簡寫形式。變量 定義 var x1,...,xn: T = e 是 var x1:T = e; ...; var xn:T = e 的簡寫形式。
Dcl ::= „type‟ {nl} TypeDcl
TypeDcl ::= id [TypeParamClause] [„>:‟ Type] [„<:‟ Type] Def ::= type {nl} TypeDef
TypeDef ::= id [TypeParamClause] „=‟ Type
類型聲明 type t[tps] >: L <: U 聲明瞭 t 是一個下界爲 L 上界爲 U 抽象類型。若是類型參數子句[type]被忽略的話,t 是一個一階類型的抽象,不然,t 就是一個類型構造器,接受類型參數子句中指明的參數。
若是一個類型聲明是一個類型的成員聲明, 該類型的實現能夠用任何符合條件L<:T<:U 的類型 T 來實現 t。若是 L 與 U 不一致將會致使編譯時錯誤。邊界的某邊或全部可被忽略。若是沒有給出下界 L,類型 scala.Nothing 就會是默認的下界。若是沒有給出上界U,類型 scala.Any 就會是默認的上界。
類型構造器聲明給與 t 有關的具體類型加上了額外的限制。除了邊界 L 和 U,類型參數子句能夠引入由類型構造器一致性(§3.5.2)控制的高階邊界和差別。
類型參數的域超越了邊界 >:L <:U 和類型參數子句 tps 自己。(抽象類型構造器 tc
的)高階類型參數子句擁有由類型參數聲明 tc 限制的一樣的域。
一些與嵌套域有關的例子,如下聲明是徹底等價的:type t[m[x] <: Bound[x], Bound[x]] , type t[m[x] <: Bound[x], Bound[Y]] 和 type t[m[x] <: Bound[x], Bound[_]],類型參數 m 的域限制爲 m 的聲明。以上全部狀況中,t 是在兩個類型構造器上抽象的抽象類型成員:m 是有一個類型參數而且是 Bound 的子類型的類型構造器,t 是第二個類型構造參數。t[MutableList, Iterable]是 t 的合法用法。
類型別名 type t = T 定義了 t 是類型 T 的別名命名。類型別名的左側能夠是一個類型參數子句,好比 type t[tps] = T。類型參數的域超過了右側的 T 和類型參數子句自己。
定義(§4)與類型參數(§4.6)的域的規則使類型命名在其自身或右側出現成爲可能。然而若是類型別名遞歸引用到定義的類型構造器自身將會致使靜態錯誤。也就是類型別名type t[tps] = T 中的類型 T 不能直接或間接的引用到命名 t。若是抽象類型是其自身直接或間接的上界或下界也會致使錯誤。
示例 4.3.1 下面是合法的類型聲明與定義:
type IntList = List[Integer] type T <: Comparable[T] type Two[A] = Tuple2[A, A]
type MyCollection[+X] <: Iterable[X]
如下是非法的:
type Abs = Comparable[Abs] //遞歸類型別名
type S <: T //S, T 自我綁定
type T <: S
type T >: Comparable[T.That] //沒法從 T 選擇
//T 是類型而不是值
type MyCollection <: Iterable //類型構造器必須顯式列出參數
若是類型別名 type t[tps] = S 指向類類型 S,命名t 也可用做類型爲S 的對象的構造器。
示例 4.3.2 下面的 Predef 對象包括了一個定義,將 Pair 做爲參數化類 Tuple2 的別名:
type Pair[+A, +B] = Tuple2[A, B]
所以,對於任意的兩個類型 S 和 T,類型 Pair[S, T]等價於類型 Tuple2[S, T]。Pair 還能夠用來做爲 Tuple2 構造器的替換。而且因爲 Tuple2 是一個 case 類,Pair 也是 case 類工廠 Tuple2 的別名,這在表達式或模式中均有效。所以如下都是 Pair 的合法使用。
val x: Pair[Int, String] = new Pair(1, 「abc」)
val y: Pair[String, Int] = x match {
case Pair(i, s) => Pair(z + i, i * i)
}
TypeParamClause ::= „[‟ VariantTypeParam {„,‟ VariantTypeParam} „]‟ VariantTypeParam ::= [„+‟ | „-‟] TypeParam
TypeParam ::= (id | „_‟) [TypeParamClause] [„>:‟ Type]
[„<:‟ Type] [„>%‟ Type]
類型參數在類型定義,類定義和函數定義中出現。本節中咱們只考慮有下界>:L 和上界<:U 的類型參數定義,視圖邊界<%U 將在 7.4 節中討論。
一階類型參數最通常的形式是+/- t >:L <:U。這裏 L 和 U 是下界和上界,限制了參數的可能的類型參量。若是 L 與 U 不一致將會致使編譯錯誤。+/-是差別,指一個可選前綴+或-。
在類型參數子句中的全部類型參數的名稱必須兩兩不一樣。類型參數的做用域在每一個類型參數子句中。所以類型參數做爲本身邊界的一部分或同一子句中其餘類型參數的邊界出現是合理的。然而,類型參數不能直接或間接的做爲本身的邊界。
類型構造參數給類型參數增長了一個嵌套的類型參數子句。最多見的類型構造器參數的形式是+/- t[tps] >: L <: U.
以上域的限制可概括爲嵌套類型參數子句,該子句聲明瞭高階的類型參數。高階類型參數(類型參數 t 的類型參數)只在他們直接包圍的參數子句(可能包括更深嵌套層次的子句)和 t 的邊界中可見。所以他們的名字只需與其餘可見參數兩兩不一樣。因爲高階類型參
數的命名每每是無關的,所以能夠用‟_‟來指示它們,這種狀況下其餘地方均不可見。
示例 4.4.1 下面是一些合法的類型參數子句:
[S, T]
[Ex <: Throwable]
[A <: Comparable[B], B <: A]
[A, B >: A, C >: A <: B]
[M[X], N[X]]
[M[_], N[_]] //和上一個等價[M[X <: Bound[X]], Bound[_]] [M[+X] <: Iterable[X]]
如下是一些非法的類型參數子句:
[A >: A] //非法,「A」作了本身的邊界 [A <: B, B <: C, C <: A] //非法,「A」作了本身的邊界
[A, B, C >: A <: B] //非法,「C」的下界「A」與上界「B」不一致
差別標註指示了參數化類型的實例在子類型(§3.5.2)上是如何不一樣的。「+」類型的差別指協變的依賴,「-」類型的差別指逆變的依賴,未標註指不變依賴。
差別標註限制了被標註類型變量在與類型參數綁定的類型或類中出現的方式。在類型定義 type T[tps] = S 或類型聲明 type T[tps] >: L <: U 中,「+」標註的類型參數只能出如今協變的位置,「-」標註的類型參數只能出如今逆變的位置。相似地,對於類定義 class C[tps](ps) exntends T x: S => ...@, 「+」標記的類型參數只能出如今類型自身S 和模板 T 的協變位置,「-」標記的類型參數只能出如今逆變位置。
類型或模板中類型參數的協變位置定義以下:與協變相反的是逆變,非變與其自身相反。最高級的類型或模板老是在協變位置。差別位置按照下述方式變化:
l 方法參數的差別位置是參數子句差別位置的相對位置。
l 類型參數的差別位置是類型參數子句差別位置的相對位置
l 類型聲明或類型參數的下界的差別位置是類型聲明或參數差別位置的相對位置
l 類型別名 type T[tps] = S 右側的S 老是處於非變位置。
l 可變量的類型老是處於非變位置
l 類型選擇 S#T 的前綴S 老是處於非變位置
l 類型 S[…T…]的類型參量 T:若是對應的類型參數是非變的,那麼 T 就在非變位置。若是對應的類型參數是逆變的,那麼 T 的差別位置就是類型 S[…T…]的差別位置的相對位置。
到類的對象私有的值,變量或方法的引用的差別並未被檢查。這些成員中類型參數能夠出如今任意位置,並未限制其合法的差別標註。
示例 4.5.1 下面的差別標註是合法的:
abstract class P[+A, +B] {
def fst: A; def snd: B
}
有了這個差別標註,類型 P 的子類型將對其參量自動協變。例如,
P[IOException, String] <: P[Throwable, AnyRef]
若是咱們使 P 的元素可變,差別標註就非法了。
abstract class Q[+A, +B](x: A, y: B) {
var fst: A = x //*** 錯誤:非法差別:
var snd: B = y //„A‟, „B‟出如今非變位置。
}
若是可變變量是對象私有的,那麼類定義就是合法的了:
abstract class R[+A, +B](x: A, y: B){
private[this] var fst: A = x //OK
private[this] var snd: B = y //OK
}
示例 4.5.2 下面的差別標註是非法的,由於a 出如今 append 的參數的逆變位置:
abstract class Vector[+A] {
def append(x: Vector[A]): Vector[A]
//**** 錯誤:非法的差別:
//‟A‟出如今逆變位置
}
這個問題能夠經過對下界求值的方式將 append 的類型泛化來解決。
abstract class Vector[+A] {
def append[B >: A](x: Vector[B]): Vector[B]
}
示例 4.5.3 下面是逆變類型參數有用的一個例子。
abstract class OutputChannel[-A] {
def write(x: A): Unit
}
有了這個標註,OutputChannel[AnyRef]將和 OutputChannel[String]一致。也就是一個能夠寫任何對象的 channel 能夠代替只能寫 String 的 channel
Dcl ::= „def‟ FunDcl
FunDcl ::= FunSig : Type
Def ::= „def‟ FunDef
FunDef ::= FunSig [„:‟ Type] „=‟ Expr
FunSig ::= id [FunTypeParamClause] ParamClauses FunTypeParamClause ::= „[‟ TypeParam {„,‟ TypeParam} „]‟
ParamClauses ::= {ParamClauses} [[nl] „(‟ „implicit‟ Params „)‟] ParamClause ::= [nl] „(‟ [Params] „)‟}
Params ::= Param {„,‟ Param}
Param ::= {Annotation} id [„:‟ ParamType]
ParamType ::= Type
| „=>‟ Type
| Type „*‟
函數聲明具備這樣的形式:def f psig: T,f 是函數的名稱,psig 是參數簽名, T 是返回類型。函數定義 def f psig: T = e 還包括了函數體 e,例如一個表達式定義了函數的結果。參數簽名由一個可選的類型參數子句[tps],後跟零個或多個值參數子句(ps1)…(psn)構成。這樣的聲明或定義引入了一個值,該值具備一個(多是多態的)方法類型,其參數類型與返回類型已給出。
已給出的函數體的類型被指望與函數聲明的返回類型一致(§6.1)。若是函數定義不是遞歸的,那麼返回類型則可省略,由於其可由函數體打包的類型推斷出來。
類型參數子句 tps 由一個或多個類型聲明(§4.3)構成,在其中引入了可能具備邊界的類型參數。類型參數的域包括整個簽名,也包括任何類型參數邊界以及函數體(若是有的話)。
值參數子句 ps 由零個或多個規範類型綁定(如 x: T)構成,這些類型綁定綁定了值參數以及將它們與它們的類型聯繫起來。一個規範值參數命名 x 的範圍是函數體(若是有的話)。全部的類型參數名及值參數名必須兩兩不一樣。
4.6.1. 叫名參數
ParamType ::= „=>‟ Type
值參數類型能夠有前綴=>,例如: x: => T。這樣一個參數的類型就是無參方法類型=>T。這代表對應的參數並無在函數應用處求值,而是在函數中每次使用時才求值。也就是該參數以叫名的方式求值。
示例 4.6.1 聲明:
def whileLoop (cond: => Boolean) (start: => Unit): Unit
表示 whileLoop 的全部參數都以叫名的方式求值。
ParamType ::= Type „*‟
參數段中的最後一個參數能夠有後綴„*‟,例如:(…, x: T*)。方法中這樣一個重複參數的類型就是序列類型 scala.Seq[T]。具備重複參數 T*的方法具備可變數目的類型爲 T 的參數。也就是,若是方法 m 的類型(T1,…,Tn,S*)U 應用到參數(e1,…,ek)上, 且有 k>=n,那麼 m 就被認爲在應用中具備類型(T1,…,Tn,S,…,S)U,S 重複 k-n 次。這個規則的惟一例外是若是最後一個參數用_*類型標註的方式被標記爲一個序列參量。若是以上的 m 應用到參數(e1,…,en,e‟:_*) 上, 那麼該應用中 m 的類型就被認爲
(T1,…,Tn,scala.Seq[S])。
示例 4.6.2 如下方法定義計算了可變數目的整形參數的和:
def sum(args: Int*) = {
var result = 0
for(arg <- args.elements) result += arg result
}
如下對該方法的應用可得出的結果爲 0,1,6:
sum()
sum(1) sum(1,2,3)
更進一步的,假設如下定義:
var xs = List(1,2,3)
如下對方法 sum 的應用是錯誤的:
sum(xs) // ***** error: expected: Int, found: List[Int]
相比較,如下應用是正確的,併產生結果 6:
sum(xs:_*)
FunDcl ::= FunSig
FunDef ::= FunSig[nl] „{‟Block„}‟
過程有特殊語法,例如,返回 Unit 值{}的函數。過程聲明只是返回類型被忽略的函數聲明。返回類型自動定義爲 Unit 類型。例如 def f(ps)等價於 def f(ps):Unit。
過程定義是返回類型及等號被忽略的函數定義;其定義表達式必須是一個代碼塊。例如:def f(ps){stats}等價於 def f(ps):Unit={stats}.
示例 4.6.3 如下是名爲write 的過程的聲明與定義:
trait Writer {
def write(str: String)
}
object Terminal extends Writer{
def write(str: String) {System.out.println(str)}
}
以上代碼被內部自動完成爲:
trait Writer {
def write(str: String): Unit
}
object Terminal extends Writer{
def write(str: String): Unit = {System.out.println(str)}
}
類成員定義 m 重載了基類 C 中的一些其餘的函數 m‟能夠略去返回類型,即便是遞歸的也無所謂。所以被重載的函數 m‟的返回類型 R‟(被認爲是 C 的成員) 在對 m 的每次調用中被認爲是 m 的返回類型。在以上方式中,m 右側的類型 R 能夠被肯定,並做爲 m 的返回類型。注意到R 能夠與R‟不一樣,只要R 與 R‟一致便可。
示例 4.6.4 假定有如下定義:
trait I {
def factorial(x: Int): Int
}
class C extends I {
def factorial(x: Int) = if (x==0) 1 else x * factorial(x - 1)
}
這裏忽略C 中 factorial 的返回類型是沒問題的,即便這是一個遞歸的方法。
Import ::= „import‟ ImportExpr {„,‟ ImportExpr} ImportExpr ::= StableId „.‟ (id | „_‟ | ImportSelectors)
ImportSelectors ::= „{‟ { ImportSelector „,‟} (ImportSelector | „_‟) „}‟ ImportSelector ::= id [„=>‟ id | „=>‟ „_‟]
import 子句形式爲 import p.I,p 是一個穩定標識符(§3.1),I 是一個 import 表達式。import 表達式肯定了 p 的成員中一些名稱的集合,使這些名稱不加限定便可用。最普通的 import 表達式的形式是一個 import 選擇器的列表。
{x1=>y1,…,xn=>yn,_}
其中 n>=0,最後的通配符„_‟能夠沒有。它使每一個成員 p.xi 在未限定的名稱 yi 下可用。例如每一個 import 選擇器 xi=>yi 將 p.xi 重命名爲 yi。若是存在最終的通配符,p 的除x1,…,xn 以外的成員z 也將在其自身未限定的名稱下可用。
import 選擇器對類型和術語成員起一樣做用。例如, import 子句 import p.{x=>y}將術語 p.x 重命名爲術語 y,而且將類型名 p.x 重命名爲類型名 y。這兩個名稱中至少有一個引用p 的一個成員。
若是 import 選擇器的目標是通配符,import 選擇器就會隱藏對源成員的訪問。例如,import 選擇器 x=>_將 x「重命名」爲通配符號(做爲用戶程序中的名稱不可訪問), 所以也有效阻止了對 x 的非限制性的訪問。這在同一個 import 選擇器列表最後有一個通 配符的狀況下是有用的,此時將引入全部前面 import 選擇器沒有說起的成員。
由 import 子句所引入的綁定的域開始於 import 子句以後並擴展至封閉塊,模板, 包子句,或編譯單元的末尾,具體決定於哪一個先出現。
存在一些簡化形式。import 選擇器能夠只是一個名字 x。這種狀況下,x 以沒有重命名的方式被引入,所以該 import 選擇器等價於 x=>x。更進一步,也能夠用一個標識符或通配符來替換整個的 import 選擇器列表。import 子句 import p.x 等價於 import p.{x},例如不用限定 p 的成員 x 便可用。import 子句 p._等價於 import p.{_},例如不用限定 p 的全部成員 x 便可用(該處是 java 中 import p.*的同義語)。一個 import 子句中的多個 import 表達式 import p1.I1,…,pn.In 被解釋爲一個
import 子句的序列 import p1.I1;…;import pn.In。
示例 4.7.1 考慮如下對象定義:
object M{
def z = 0, one = 1
def add(x: Int, y: Int):Int = x + y
}
所以代碼塊
{import M.{one, z => zero, _}; add(zero, one)}
就等價於代碼塊
{M.add(M.z, M.one)}
TmplDef ::= [„case‟] „class‟ ClassDef
| [„case‟] „object‟ ObjectDef
| „trait‟ TraitDef
ClassTemplate ::= [EarlyDefs] ClassParents [TemplateBody] TraitTemplate ::= [EarlyDefs] TraitParents [TemplateBody] ClassParents ::= Constr {„with‟ AnnotType}
TraitParents ::= AnnotType {„with‟ AnnotType}
TemplateBody ::= [nl] „{‟[SelfType] TemplateStat{semi TemplateStat}„}‟ SelfType ::= id [„:‟ Type] „=>‟
| this „:‟ Type „=>‟
模板定義了對象的特徵或對象的類或單個對象的類型簽名,行爲和初始狀態。模板是實例建立表達式,類定義和對象定義的一部分。模板 sc with mt1 with ... with mtn {stats}包括一個構造器調用 sc,定義了模板的超類;以及特徵引用 mt1,...,mtn (n>=0),定義了模板的特徵;和一個語句序列 stats,包括初始化代碼和模板額外的成員定義。
每一個特徵引用 mti 必須表示一個特徵(§5.3.3),做爲對比,超類構造器 sc 通常指向 一個類而不是特徵。能夠寫出由特徵引用開始的一系列的父類,好比 mt1 with ... with mtn。在這種狀況下父類列表被自動擴展以包括 mt1 的超類,並做爲第一個父類型。新的超類應當有至少一個無參數構造器。在之後內容中,咱們將老是假定該自動擴展已經 被執行,所以模板的第一個父類是一個正規的超類構造器,而不是一個特徵引用。
每一個類的父類列表也老是自動擴展至 scala.ScalaObject 特徵作爲最後一個混入類型。例如:
sc with mt1 with ... with mtn {stats}
將變爲
mt1 with ... with mtn {stats} with ScalaObject {stats}
模板的父類列表必須格式正確。也就是由超類構造器 sc 指示的類必須是全部特徵mt1,...,mtn 的超類的子類。換句話說,模板繼承的非特徵類在繼承層級中構成了一個鏈, 其起始爲模板的超類。
模板的超類型的最低要求是類類型或複合類型(§3.2.7)由其全部的父類類型構成。語句序列 stats 包括成員定義,定義了新成員或覆蓋父類中的成員。若是模板構成了
抽象類或特徵的定義,語句部分 stats 還能夠包括抽象成員的聲明。若是模板構成了實體類的定義,stats 仍能夠包括抽象類型成員的聲明,可是不能包括抽象術語成員。更進一步,stats 還能夠包括表達式;這些將以他們給定的順序做爲模板的初始化步驟的一部分來執行。
模板語句的序列能夠有一個正式的參數定義和一個箭頭做爲前綴,例如 x=>或 x:T
=>。若是給出了一個正式的參數,這個參數在模板體中可用做引用 this 的別名。若是給出了正式的參數的類型 T,這個定義就會如下面的方式影響類或對象的自類型 S:設 C 是定義了模板的類、特徵或對象的類型,若是給定正式的自參數類型 T,S 就是 T 和 C 的最大下界。若是沒有給出T,S 就是C。在模板內,this 的類型就會被假定爲S。
類或者對象的自類型必須與模板 t 繼承的全部類的自類型一致。
自類型標註的第二種形式是 this: S=>。它規定了 this 的類型 S,但並無爲其引入別名。
示例 5.1.1 考慮如下的類定義
class Base extends Object{} trait Mixin extends Base{} object O extends Mixin{}
這種狀況下,O 的定義可擴展爲
object O extends Base with Mixin{}
繼承自 Java 類型 模板能夠有一個 Java 類做爲其超類,或者 Java 接口做爲其混入。
模板求值 考慮模板 sc with mt1 with mtn {stats}。若是這是特徵(§5.3.3)的一個模板,那麼其混入求值由語句序列 stats 的求值構成。
若是這不是一個特徵的模板,那麼其求值包括如下步驟:
l 首先,對超類的構造器sc(§5.1.1)求值
l 而後,模板線性化中的全部基類,直到有 sc 表示的模板的超類將會作混入求值。混入求值的順序是線性化中出現順序的反序,好比緊挨 sc 前的類會被第一個求值。
l 最後對語句序列 stats 求值
5.1.1. 構造器調用
Constr ::= AnnotType {„(‟ [Exprs [„,‟]] „)‟}
構造器調用定義了類型,成員以及由實例建立表達式或由類或對象定義繼承的對象定義 創 建 的 對 象 的 初 始 狀 態 。 構 造 器 調 用 是 一 個 函 數 應 用x.c[targs](args1)...(argsn),x 是一個穩定的標識符(§3.1),c 是一個指向類或
定義別名類型的類型名,targs 是一個類型參量列表,args1,...,argsn 是參量列表, 與該類的某個構造器的參數匹配。
前綴‟x.‟能夠省略。類型參數列表只在類 c 須要類型參數時纔給出。即便這樣這也能夠在使用本地類型推斷(§6.25.4)能夠合成參數列表時忽略。若是沒有顯式的給出參量, 就會默認給一個空參量列表()。
構造器調用 x.c[targs](args1)...(argsn)的執行包括如下幾個步驟:
l 首先對前綴 x 求值
l 而後參量 args1,...,argsn 按照從左至右的順序求值。
l 最後,對c 指向的類的模板求值,初始化正在被建立的內容。
經過類 C 可達的直接繼承關係的傳遞閉包可達的類稱爲 C 的基類。因爲混入的關係, 基類的繼承關係基本上構成一個直接非循環圖。這個圖的線性化以下定義:
定義 5.1.2 設類 C 有模板 C1 with ... with Cn { stats }。C 的線性化 L(C)定義以下:
L(C) = C, L(Cn) L(C1)
這裏 表示串聯,算符右側的元素替換算符左側標識的元素
{a,A} B = a,(A B) 若是 a¢B
= (A B) 若是 a B
示例 5.1.3 考慮如下的類定義
abstract class AbsIterator extends AnyRef { }
trait RichIterator extends AbsIterator { }
class StringIterator extends AbsIterator { }
class Iter extends StringIterator with RichIterator { }
那麼類 Iter 的線性化就是:
{ Iter, RichIterator, StringIterator, AbsIterator, ScalaObject, AnyRef, Any }
特徵 ScalaObject 出如今列表裏是由於每一個 Scala 類(§5.1)都會添加它做爲最後的混入。
注意一個類的線性化優化了繼承關係:若是 C 是 D 的子類,那麼 C 就會在任何 C 和 D 同時出現的線性化中出如今 D 前面。定義 5.1.2 也知足一個類的線性化老是包括其直接超類的線性化做爲其後綴這個性質。例如,StringIterator 的線性化就是:
{ StringIterator, AbsIterator, ScalaObject, AnyRef, Any }
這就是其子類 Iter 的線性化的後綴。對混入的線性化卻不是這樣。例如,
RichIterator 的線性化是:
{ RichIterator, AbsIterator, ScalaObject, AnyRef, Any}
這並非 Iter 線性化的後綴。
由模板 C1 with ... with Cn { stats }定義的類 C 能夠在語句序列 stats 中定義成員和繼承全部父類的成員。Scala 採起了 Java 和 C#的方法靜態重載的方便之處。所以一個類能夠定義和/或集成多個同名方法。要肯定類 C 定義的成員是否覆蓋了父類的成員,或C 中兩個同時存在的重載的變量,Scala 使用瞭如下的成員匹配定義:
定義 5.1.4 成員定義 M 與成員定義 M‟,匹配的條件是:首先他們綁定了一樣的名稱,而後符合下面中的一條:
成員定義有兩類:實體定義與抽象定義。類 C 的成員要麼直接定義(例如出如今 C 的語句序列 stats 中)或繼承。有兩條規則來肯定類的成員集合,每一個分類一條:
定義 5.1.5 類 C 的實體成員是某些類 Ci L(C)中的實體定義 M,除非在前置的類
Cj L(C)(j<i)中有一個與 M 匹配的直接定義的實體成員 M‟。
類 C 的抽象成員是某些類 Ci L(C)中任意抽象定義的 M,除非 C 已經包括一個與 M 匹配的實體成員 M‟,或者在前置類 Cj L(C)且 j<i 中已經有一個與 M 匹配的直接定義的抽象成員M‟。
該定義也肯定了類 C 及其父類(§5.1.4)中匹配的成員的重載關係。第一,實體定義老是覆蓋抽象定義。第二,若是 M 和 M‟都是實體的或抽象的,只有 M 定義的類在(C 的線性化中)M‟所在類的前面出現時,M 纔會重載 M‟。
一個模板定義了兩個匹配的成員將會致使錯誤。一個模板包括兩個同名且具備一樣擦除(§3.7)類型的成員(直接定義或繼承)也將會致使錯誤。
示例 5.1.6 考慮如下特徵定義
trait A { def f: Int }
trait B extends A{def f:Int = 1;def g:Int = 2;def h:Int=3}
trait C extends A{override def f:Int = 4; def g:Int}
trait D extends B with C{def h:Int}
特徵 D 有一個直接定義的抽象成員 h。它從特徵 C 繼承了成員 f,從特徵 B 繼承了成員 g。
類 C 的成員 M 與(§5.1.3)中定義的 C 的基類的非私有成員 M‟一致可定義爲覆蓋該成員。在此狀況下覆蓋成員 M 的綁定必須包含(§3.5.2)被覆蓋的成員 M‟的綁定。另外如下限制應用於M 和M‟的修飾符:
l M‟不能標記爲 final。
l M 不能是 private(§5.2)。
l 若是 M 在某些封閉類或包 C 中標記爲 private[C],那麼 M‟必須在類或包 C‟中標記爲 private[C‟],且 C‟等於 C 或 C‟包含於 C。
l 若是M 標記爲 protected,那麼 M‟也必須是 protected。
l 若是M‟不是一個抽象成員,那麼 M 必須標記爲 override
l 若是M‟在 C 中是不完整(§5.2)的,那麼 M 必須標記爲 abstract override
l 若是 M 和 M‟都是實體值定義,那麼他們必須都標記爲 lazy 或者都不標記爲
對於無參數方法有個特例。若是一個無參數方法定義爲 def f: T = ...或者 def f = ...覆蓋了類型()T‟中的一個空參數列表方法,那麼 f 也被假定爲具備一個空參數列表。
示例 5.1.7 考慮如下定義
trait Root { type T <: Root}
trait A extends Root { type T <: A} trait B extends Root { type T <: B} trait C extends A with B
那麼類 C 的定義是錯誤的,由於 C 中 T 的綁定是 type T <: B,不能包含綁定 A 中的 type T <: A。該問題可經過在類C 中添加T 的覆蓋定義來解決。
class C extends A with B { type T <: C}
設 C 爲類類型。C 的繼承閉包就是如下類型的最小集合 υ:
l 若是T 在υ 中,那麼語法上構成 T 的每一個類型 T‟也在 υ 中。
l 若是T 是υ 中的一個類類型,那麼T 的全部父類(§5.1)也在 υ 中。
若是類類型的繼承閉包包含無窮個類型將會致使靜態錯誤。(該限制對於使子類型可推斷[KP07]是必要的)。
EarlyDefs ::= „{‟ [EarlyDef {semi EarlyDef}] „}‟ „with‟
EarlyDef ::= {Annotation} {Modifier} PatVarDef
模板開頭能夠是前置字段定義子句,該子句在子類型構造器被調用以前定義了字段的值。在如下模板中
{ val p1: T1 = e1
...
val pn: Tn = en
} with sc with mt1 with mtn {stats}
初始模式定義 p1,...,pn 被稱爲前置定義。他們定義了構成模板的字段。每一個前置定義必須定義至少一個變量。
前置定義在模板被定義與賦類型參數以及在此以前的任意前置定義以前作類型檢查與求值,參數能夠是類的任意類型參數。在前置定義中在右側任何對 this 的引用指模板之
外的 this 標識符。所以,前置定義不可能引用模板建立的對象,或者引用其字段與方法, 除了同一段落中前面的前置定義以外。再者,對前面的前置定義的引用老是引用那裏定義 的值,並不牽涉到覆蓋的定義。
換句話說,前置定義的代碼塊以包含一些值定義的局部代碼塊的形式求值。前置定義在模板的父類構造器被調用以前以他們被定義的順序求值。
示例 5.1.8 前置定義在特徵中特別有用,它們沒有一般的構造器參數。例如:
trait Greeting {
val name: String
val msg = 「How are you, 「+name
}
class C extends {
val name = 「Bob」
} with Greeting{ println(msg)
}
在以上代碼中,字段 name 在 Greeting 的構造器以前被初始化。類 Greeting 中的字段 msg 被正確初始化爲」How are you, Bob」。
若是 name 不是在 C‟的類主體中被初始化,而是在 Greeting 的構造器以後初始化。在此狀況下,msg 會被初始化爲」How are you, <null>」。
Modifier ::= LocalModifier
| AccessModifier
| „override‟ LocalModifier ::= „abstract‟
| „final‟
| „sealed‟
| „implicit‟
| „lazy‟
AccessModifier ::= („private‟ | „protected‟) [AccessQualifier] AccesQualifier ::= „[‟ (id | „this‟) „]‟
成員定義前的修飾符會影響其標定的標識符的可見性及使用。若是給出了多個修飾符, 其順序沒有關係,可是同一個修飾符不能重複出現。重複定義前的修飾符將應用於全部定 義上面。控制修飾符的有效性與含義的規則以下:
l private 修飾符能夠應用在模板的任何定義與聲明上。這些成員只能被直接封閉的模板和其伴隨模塊及伴隨類訪問(示例 5.4.1)。他們不能被子類繼承,也不能覆蓋父類中的定義。
此修飾符可由一個標識符 C 限定(如 private[C]),表示該類或包包含該定義。由該標識符標識的成員只能由包 C 或類 C 以及他們的伴隨模塊(§5.4)來訪問。
這些成員也僅能由C 內的模板訪問。
限定的一個特殊形式是 private[this]。由該標識符標記的成員 M 只能從該成員定義的對象內訪問。也就是選擇 p.M 只有在前綴是 this 和包含該引用的類 O 的 O.this 時才合法。這也就是沒有加限定的 private 的應用。
標記爲沒有限定的 private 的成員稱爲類私有,標記爲 private[this]的成員稱爲對象私有。無論是類私有仍是對象私有均可以稱爲私有成員, 可是private[C]不是,C 是一個標識符,在後者該成員稱爲限定私有。
類私有或對象私有成員不能是抽象的,而且不能再由 protected,final 或
override 修飾符限定。
l protected 標識符應用到類成員定義上。類的保護成員能夠從如下位置訪問:
- 定義的類模板內
- 全部以定義的類爲基類的模板
- 任何這些類的伴隨模塊
protected 修飾符能夠由一個標識符 C 來限定(例 protected[C]),C 必須是一個包含該定義的類或者包。由該修飾符標記的成員能夠被包或者類 C 內的全部代碼及伴隨模塊(§5.4)訪問。
一個 protected 標識符x 可在選擇 r.x 做爲成員名稱的條件是:
- 訪問是在定義該成員的模板內;若是給出了限定 C 的話,就是在包或者類 C
以及其伴隨模塊內,或者:
- r 是保留字 this 和 super 中的一個,或者:
- r 的類型與包含訪問的類的類型實例一致
限定的一個特殊形式是 protected[this]。被此修飾符標記的成員 M 只能從其定義的對象內訪問。也就是選擇 p.M 只有在前綴是 this 或者 O.this 的時候才合法(類O 包含該引用)。這也就是未加限定的 protected 應用的方式。
l override 修飾符應用於類成員定義或聲明。對於那些覆蓋了父類中某些實體成員定義的成員定義與聲明,該修飾符是必須的。若是給出了 override 修飾符, 那麼應當至少有一個被覆蓋的成員定義或聲明(能夠是實體的或者抽象的)
l 當 override 和 abstract 一塊兒出現時具備顯著不一樣的意義。該修飾符組合僅用於特徵的值成員。標記爲 abstract override 的成員必須覆蓋至少一個其餘成員,全部被其覆蓋的成員必須是不完整的。
成員 M 是不完整的條件是: M 是抽象的(例如由一個聲明定義)或者標記爲
abstract 和 override,這樣即便是被 M 覆蓋的成員也成爲不完整的。
注意修飾符組合 abstract override 並不影響一個成員是否是實體或者抽象的概念。若是一個成員僅給出了一個聲明,那麼它就是抽象的;若是給出了完整定義,那麼它就是實體的。
l abstract 修飾符可用於類定義。但不必用於特徵。對於具備不完整成員的類來講是必須的。抽象類不能經過構造器調用初始化(§6.10),除非後跟覆蓋了類 中全部不完整成員的混入和/或修飾體。只有抽象類和特徵能夠有抽象術語成員。正如前文所述,abstract 修飾符能夠和 override 連用,應用於類成員定義。
l final 修飾符應用於類成員定義和類定義。標記爲 final 的類成員定義不能在子類中被覆蓋。標記爲 final 的類不能被模板繼承。final 對於對象定義是多餘的。標記爲 final 的類或對象的成員隱含定義爲 final 的,因此對它們來講final 修飾符也是多餘的。final 修飾符不能修飾不完整成員,而且在修飾符列表中不能與 private 或 sealed 組合。
l sealed 修飾符應用於類定義。標記爲 sealed 的類不能被直接繼承,除非繼承的模板和該類定義於同一源文件。然而 sealed 的類的子類能夠在任何地方繼承。
l lazy 修飾符應用於值定義。標記爲 lazy 的值只在其第一次被訪問(可能永遠也不發生)時初始化。試圖在值初始化時訪問該值可能致使循環行爲。若是在初始化時有異常被拋出,該值則被認爲沒有被初始化,隨後的訪問將會繼續嘗試對其右側表達式求值。
示例 5.2.1 如下代碼列舉了限定私有的用法:
package outerpkg.innerpkg
class Outer {
class Inner { private[Outer] def f() private[innerpkg] def g() private[outerpkg] def h()
}
}
在這裏對方法 f 的訪問能夠出如今 OuterClass 的任何地方,但不能在其外面。對方法 g 的訪問能夠出如今包 outerpkg.innerpkg 的任何地方,和 Java 中的包私有方法相似。最後,對方法 h 的訪問能夠出如今包 outerpkg 的任何地方,包含其包括的全部包。
示例 5.2.2 阻止類的使用者去建立該類的新實例的一個經常使用方法是將該類聲明爲
object m {
abstract sealed class C (x: Int) {
def nextC = new C(x + 1) {}
}
val empty = new C(0){}
}
例如以上的代碼用戶只能經過調用 m.C 中的 nextC 方法來建立類 m.C 的實例。用戶沒法直接建立類m.C 的對象。如下兩行是錯誤的:
new m.C(0) //*** 錯誤:C 是抽象的,不能初始化
new m.C(0){} //*** 錯誤:從 sealed 類非法繼承
也能夠經過將主要構造器標記爲 private 來達成這一目的(參見示例 5.3.2)。
TmplDef ::= „class‟ ClassDef
ClassDef ::= id [TypeParamClauses] {Annotation} ClassParamClauses ::= {ClassParamClause}
[[nl] „(‟ implicit ClassParams „)‟] ClassParamClause ::= [nl] „(‟ [ClassParams] „)‟
ClassParams ::= ClassParam {„,‟ ClassParam}
ClassParam ::= {Annotation} [{Modifier} („val‟ | „var‟)] Id [„:‟ ParamType]
ClassTemplateOpt ::= „extends‟ ClassTemplate
| [[„extends‟] TemplateBody]
類定義最多見的形式是
class c[tps] as m(ps1)...(psn) extends t (n>=0).
此處:
C 是要定義的類的名稱
tps 是要定義的類的類型參數的非空列表。類型參數的做用域是整個類定義,包括類型參數段自身。用同一個名稱來定義兩個類型參數是非法的。類型參數段[tps] 能夠沒有。具備類型參數段的類稱爲多態的,不然稱爲單態的。
as 是一個可爲空的標註(§11) 序列。若是給出了標註,他們將應用於類的主構造器。
m 是訪問修飾符(§5.2),好比 private 或者 protected,能夠有一個限定。若是給出了這樣一個訪問修飾符,它將應用於類的主構造器。
(ps1)...(psn)是類的主構造器的正式值參數子句。正式值參數的做用域包含模板 t。然而一個正式值參數並非任何父類或類型模板 t 成員的一部分。用同一個名稱來定義兩個正式值參數是非法的。若是沒有給出正式參數段,則會假定有一個空的參數段()。
若是正式參數聲明 x:T 前面有 val 或者 var 關鍵字,針對該參數的一個訪問器(getter)定義(§4.2)將會被自動加入類中。getter 引入了類 c 的值成員 x,其定義是該參數的別名。若是引入關鍵字是 var,一個 setter 訪問器 x_=(§4.2)也會被自動加入到類中。調用該 setter x_=(e)將會將參數的值變爲 e 的求值結果。正式參數聲明能夠包含修飾符,並將會自動傳遞給訪問器定義。一個有 val 或者 var 前綴的正式參數不能同時成爲叫名參數(§4.6.1)。
t 是一個模板(§5.1),具備如下形式:
sc with mt1 with ... with mtm ( stats ) (m>=0)
該模板定義了類的對象的基類,行爲和初始狀態。extends 子句 extends sc
with mt1 ... with mtm 能夠被忽略,默認爲 extends scala.AnyRef。類體
{stats}也能夠沒有,默認爲空{}
這個類定義定義了一個類型 c[tps]和一個構造器,當該構造器應用於與類型 ps 一致的參數時將會經過對模板 t 求值來建立類型 c[tps]的實例。
示例 5.3.1 如下例子展現了類 C 的 val 和 var 參數
class C(x: Int, val y: String, var z: List[String])
var c = new C(1, 「abc」, List())
c.z = c.y :: c.z
示例 5.3.2 下面的類只能從其伴隨模塊中建立
object Sensitive {
def makeSensitive(credentials: Certificate): Sensitive =
if (credentials == Admin) new Sensitive()
else throw new SecurityViolationException
}
class Sensitive private (){
...
}
5.3.1. 構造器定義
FunDef ::= „this‟ ParamClause ParamClauses
(„=‟ ConstrExpr | [nl] ConstrBlock) ConstrExpr ::= SelfInvocation
| ConstrBlock
ConstrBlock ::= „{‟ SelfInvocation {semi BlockStat} „}‟ SelfInvocation ::= „this‟ ArgumentExprs {ArgumentExprs}
一個類能夠有除主構造器外的構造器。這些由形如 def this(ps1)...(psn) = e 之類的構造器定義所定義。這樣的定義在類內引入了額外的構造器,並具備以正式參數列表 ps1,...,psn 形式的參數,其求值由構造器表達式 e 所定義。每一個正式參數的做用域是構造器表達式 e。一個構造器表達式能夠是一個構造器自調用 this(args1) ... (argsn)或者一個以構造器自調用開始的代碼塊。構造器自調用必須建立一個類的通用實例。例如,若是問題中的類具備名稱 C 和類型參數[tps],那麼構造器自調用必須產生一個 C[tps]的實例;初始化正式類型參數是不容許的。
一個構造器定義中的簽名及構造器自調用是有類型檢查的,並在類內產生做用域的地方求值,能夠加該類的任何類型參數以及該模板的任何前置定義(§5.1.6)。構造器的其餘部分會被類型檢查並以當前類中一個函數體的形式求值。
若是類 C 有輔助構造器,這些構造器與 C 的主構造器(§5.3)構成了重載的構造器定義。重載解析(§6.25.3)的一般規則應用於 C 的構造器調用,包括構造器表達式中的構造器自調用。然而,不一樣於其餘方法,構造器從不繼承。爲了防止構造器調用的無限循環, 限制了每一個構造器自調用只能引用它前面定義的構造器(例如它只能引用前面的輔助構造器或類的主構造器)
示例 5.3.3 考慮如下類定義:
class LinkedList[A]() {
var head = _
var tail = null
def isEmpty = tail != null
def this(head: A) = { this(); this.head = head }
def this(head: A, tail: List[A]) = { this(head); this.tail = tail}
}
這裏定義了類 LinkedList 和三個構造器。第二個構造器建立了一個單值列表,第三個構造器建立了一個給出了 head 和 tail 的列表。
TmplDef ::= „case‟ „class‟ ClassDef
若是一個類定義有 case 前綴,那麼該類就被稱爲 case 類
case 類中第一個參數段中的正式參數稱爲元素,對它們將做特殊處理。首先,該參數的值能夠擴展爲構造器模式的一個字段。其次,該參數默認添加 val 前綴,除非該參數已經有 val 或 var 修飾符。而後會針對該參數生成一個訪問定義(§5.3)。
case 類定義 c[tps](ps1)...(psn)有類參數 tps 和值參數 ps,會自動生成一個擴展對象(§8.1.7),定義以下:
object c {
def apply[tps](ps1)...(psn): c[tps] = new c[Ts](xs1)...(xsn)
def unapply[tps](x: c[tps]) = scala.Some(x.xs11,...,x.xs1k)
}
這裏,Ts 是類型參數段 tps 中定義的類型向量,每一個 xsi 表示參數段 psi 中的參數名稱,xs11,...,xs1k 表示第一個參數段 xs1 中全部的參數名。若是類中沒有類型參數段, 那麼 apply 和 unapply 方法也就沒有了。若是類 c 是 abstract 的,apply 的定義會被忽略。若是 c 的第一個參數段 ps1 以一個(§4.6.2)中的重複參數結尾,unapply 方法的名稱會改成 unapplySeq。若是已經存在伴隨對象 c,則不會建立新的對象,可是apply 和 unapply 方法會添加進現有對象中。
每一個 case 類都自動重載類 scala.AnyRef(§12.1)中的一些方法定義,除非 case 類自己已經給出了該方法的定義或在 case 類的某些基類中已經有與 AnyRef 中的方法不一樣的實體方法定義。特別是:
equals: (Any)Boolean 方法是結構相等的,兩個實例相等的條件是他們都屬於問題中的 case 類,且他們具備相同的構造器參數。
hashCode: Int 方法計算一個哈希碼。若是數據結構成員的 hashCode 方法產生對應相等的哈希值,那麼 case 類的 hashCode 產生的值也要相等。
toString: String 方法返回一個包含類名和其元素的字符串表示。
示例 5.3.4 如下是 lambda 演算的抽象語法定義
class Expr
case class Var (x: String) extends Expr case class Apply (f: Expr, e: Expr) extends Expr case class Lambda (x: String, e: Expr) extends Expr
此處定義了一個類 Expr 和 case 類 Var, Apply 和 Lambda。一個 lambda 表達式的傳值參數計算器能夠寫爲以下方式:
type Env = String => Value
case class Value(e: Expr, env: Env)
def eval(e: Expr, env: Env): Value = e match {
case Var(x) => env(x)
case Apply(f, g) =>
val Value(Lambda (x, e1), env1) = eval(f, env)
val v = eval(g, env)
eval (e1, (y => if (y == x) v else env1(y)))
case Lambda(_,_) => Value(e, env)
}
能夠經過在程序的其餘地方擴展類型 Expr 來定義更多的 case 類,例如:
case class Number(x: Int) extends Expr
能夠經過將基類 Expr 標記爲 sealed 來移除擴展性;在此狀況下,全部直接擴展
Expr 的類必須與 Expr 在同一源文件中
TmplDef ::= „trait‟ TraitDef
TraitDef ::= id [TypeParamClause] TraitTemplateOpt TraitTemplateOpt ::= „extends‟ TraitTemplate
| [[„extends‟] TemplateBody]
特徵是那些要以混入的形式加入到其餘類中的類。與一般的類不一樣,特徵不能有構造器參數。且也不能有構造器參數傳遞給其父類。這些都是不必的,由於特徵在父類初始化後才進行初始化。
假定特徵 D 定義了類型 C 的實例 x 的某些特色(例如 D 是 C 的一個基類)。那麼 x 中D 的實際超類型是 L(C)中超越 D 的全部基類的複合類型。實際超類型給出了在特徵中解析 super 的上下文(§6.5)。要注意到實際超類型依賴於特徵所添加進的混入組合,當定義特徵時是沒法知道的。
若是D 不是特徵,那麼它的實際超類型就是其最小合適超類型(實際在定義時可知) 示例 5.3.5 如下特徵定義了與某些類型的對象能夠比較的屬性。包括一個抽象方法<和其餘比較算符<=,>和>=的默認實現。
trait Comparable[T <: Comparable[T]] { self: T =>
def < (that: T): Boolean
def <=(that: T): Boolean = this < that || this == that
def > (that: T): Boolean = that < this def >=(that: T): Boolean = that <= this
}
示例 5.3.6 考慮抽象類 Table 實現了由鍵類型 A 到值類型 B 的映射。該類有一個方法set 來將一個新的鍵值對放入表中,和方法 get 來返回與給定鍵值匹配的可選值。最後, 有和 get 方法相似的方法 apply,只是若是表中沒有給定鍵的定義該方法將會返回一個給定的默認值。該類實現以下:
abstract class Table[A, B](defaultValue: B) {
def get(key: A): Option[B]
def set(key: A, value: B)
def apply(key: A) = get(key) match {
case Some(value) => value
case None => defaultValue
}
}
如下是 Table 類的實際定義。
class ListTable[A, B](defaultValue: B) extends Table[A, B](defaultValue){
private var elems: List[(A,B)]
def get(key: A) = elems.find(._1.==(key)).map(._2)
def set(key: A, value: B) = { elems = (key, value) :: elems }
}
如下是一個特徵來防止對父類中 get 和 set 操做的併發訪問:
trait SynchronizedTable[A, B] extends Table[A, B] {
abstract override def get(key: A): B = synchronized { super.get(key) }
abstract override def set(key: A, value: B) = synchronized { super.set(key, value) }
}
注意 SychronizedTable 並無傳遞給父類 Table 參數,即便 Table 定義了正式參數。一樣注意到 SynchronizedTable 的 get 和 set 方法中對 super 的調用靜態地引用了父類 Table 中的抽象方法。這是合法的, 由於該方法標記爲 abstract override (§5.2)。
最後,如下混入組合建立了一個同步的列表,以字符串做爲鍵,以整數做爲值,並定義 0 爲缺省值。
object MyTable extends ListTable[String, Int](0) with SynchronizedTable
對象 MyTable 從 SynchronizedTable 中繼承了 get 和 set 方法。這些方法中對super 的調用與對應的 ListTable 中的對應方法從新綁定,實際就是 MyTable 中SynchronizedTable 中的實際超類型。
ObjectDef ::= id ClassTemplate
對象定義定義了一個新類的單個對象。最經常使用的形式是 object m extends t。這裏 m 是要定義的對象的名稱,t 是一個具備如下形式的模板(§5.1)
sc with mt1 with ... with mtn { stats }
此處定義了 m 的基類,行爲以及初始狀態。extends 子句 extends sc with mt1 with ... with mtn 可忽略,默認是 extends scala.AnyRef。類體 {stats}也可被忽略,默認爲空{}。
對象定義定義了與模板 t 一致的單個對象(或:模塊)。它大概等同於如下的三個定義, 定義了一個類並按需建立了該類的單個對象。
final class m$cls extends t private var m$instance = null final def m = {
if (m$instance == null) m$instance = new m$cls m$instance
}
若是該定義是代碼塊的一部分則這裏的 final 修飾符可忽略。名稱 m$cls 和
m$instance 不可從用戶程序中訪問。
注意到對象定義的值是懶加載的。構造器 new m$cls 並非在對象定義時求值,而是在程序執行是 m 第一次被去引用時(可能永遠也不會發生)。試圖再次對構造器求值來從新去引用將致使死循環或運行時錯誤。
然而以上討論並不能應用於頂級對象。不能這樣的緣由是變量和方法定義不能出如今頂級。而頂級對象將被翻譯爲靜態字段。
示例 5.4.1 Scala 中的類沒有靜態成員;然而能夠經過對象定義來達到等價的效果,例如:
abstract class Point {
val x: Double
val y: Double
def isOrigin = (x == 0.0 && y == 0.0)
}
object Point {
val origin = new Point() { val x = 0.0; val y = 0.0 }
}
這裏定義了一個類 Point 和一個包含成員 origin 的對象 Point。注意兩次使用名稱 Point 是合法的,由於類定義在類型命名空間中定義了名稱 Point,而對象定義在術語命名空間中定義了Point
Scala 編譯器在解釋一個具備靜態成員的 Java 類時使用了這種技術。這樣的一個類C 能夠在概念上認爲是包括全部的C 的實例成員的一個 Scala 類,和包括全部 C 的靜態成員的一個 Scala 對象的一對組合。
一般來說,一個類的伴隨模塊是和類具備一樣名稱的一個對象,並定義在一樣的做用域和編譯單元中。一樣地,這個類能夠稱做該模塊的伴隨類。
Expr ::= (Bindings | id | „_‟) „=>‟ Expr
| Expr1
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 ::= Expr {„,‟ Expr} BlockExpr ::= „{‟ CaseClauses „}‟
| „{‟ Block „}‟
Block ::= {BlockStat semi} [ResultExpr] ResultExpr ::= Expr1
| (Bindings | (id | „_‟) „:‟ CompoundType) „=>‟ Block Ascription ::= „:‟ InfixType
| „:‟ Annotation {Annotation}
| „:‟ „_‟ „*‟
表達式由算符和操做數構成。如下將按照以上順序的降序來討論表達式的形式。
表達式的類型化每每和某些指望類型有關(多是沒有定義的)。當咱們說「表達式 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 }
SimpleExpr ::= Literal
字面值的類型化如(§1.3)中所述;它們的求值是當即可得的。字面值的另一個形式指明類。形式以下:
classOf[C]
這裏 classOf 是在 scala.Predef(§12.5)中定義的一個方法,C 是一個類類型。該類字面值的值是類類型C 的運行時表示。
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。
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。
須要穩定類型的上下文須要知足如下條件:
pt 不一致,或
選擇 e.x 在限定表達式 e 第一次求值時求值,同時產生一個對象 r。選取的結果是 r 的成員要麼由 m 定義或由重載 m 的定義所定義。若是該成員具備與 scala.NotNull 一致的 類 型 , 該 成 員 的 值 必 須 初 始 化 爲 與 null 所不一樣的值,不然 將拋出scala.UnitializedError。
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 必須是類型或方法。若是是方法則必須是實體方法,若是是模板則必須是包含擁有重載了
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 混用將返回不一樣的值。
SimpleExpr ::= SimpleExpr1 ArgumentExprs ArgumentExprs ::= „(‟ [Exprs [„,‟]] „)‟
| „(‟ [Exprs „,‟] PostfixExpr „:‟ „_‟ „*‟ „)‟
| [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))
卻沒法經過類型檢查。
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)
Array.range _ (x1, x2) => Array.range(x1, x2)
List.map2 _ (x1, x2) => (x3) => List.map2(x1, x2)(x3) List.map2(xs, ys)_ x=> List.map2(xs, ys)(x)
要注意到在方法名和其後跟下劃線間必需要有空格,不然下劃線將會被認爲是方法名的一部分。
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)能夠經過實際函數參量類型和指望結果類型來獲得一個多態函數的最佳類型參數,則類型應用能夠忽略。
SimpleExpr ::= „(‟ [Exprs [„,‟]] „)‟
元組表達式(e1,...,en)是類實例建立 scala.Tuplen(e1,...,en)的別名(n>=2)。該表達式後面還能夠有個分號,例如(e1,...,en,)。空元組()是類型 scala.Unit 的惟一值。
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 求值來初始化。表達
式的類型爲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 是某個新建立的名稱。
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 一 個 本 地 定 義 的 類 型 定 義 typet[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 求值,該表達式定義了代
碼塊的結果。
示例 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。
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)。
後綴算符能夠是任意標識符。後綴操做 e op 被解釋爲 e.op。
中綴算符能夠是任意標識符。中綴算符的優先級和相關性定義以下:
中綴算符的優先級由算符的第一個字符肯定。字符按照優先級升序在下面列出,同一行中的字符具備一樣的優先級。
(全部字母)
|
^ &
< >
= !
:
+ -
* / %
(全部其餘特殊字符)
也就是說,由字母開頭的算符具備最低的優先級,而後是由„|‟開頭的算符,下同。這個規則中有一個例外,就是賦值算符(§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 是一個新的名稱。
賦值算符是一個由等號「=」結尾的算符記號((§1.1)中的語法類 op),但具備如下條件的算符除外:
(1) 算符也由等號開始,或
(2) 算符是(<=), (>=), (!=)中的一個
賦值算符作特殊處理,若是沒有其它有效的解釋,則擴展爲賦值。
咱們考慮一個賦值算符,好比+=。在中綴運算 l += r 中,l 和 r 是表達式。該運算能夠從新解釋爲負責賦值的運算
l = l + r
除非該運算的作的l 只計算一次。在如下兩種條件下會發生再解析。
Expr1 ::= PostfixExpr „:‟ CompoundType
類型化的表達式 e:T 具備類型 T。表達式 e 的類型被指望與 T 一致。表達式的結果就是 e 的值轉化爲類型T。
示例 6.13.1 如下是合法與非法類型化的表達式的例子
1: Int //合法,類型爲 Int
1: Long //合法,類型爲 Long
//1: string //*****非法
Expr1 ::= PostfixExpr „:‟ Annotation {Annotation}
標註表達式 e: @a1 ... @an 將標註 a1,...,an 附在表達式e(§11)上。
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
}
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
}
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 一致。
Expr1 ::= „while‟ „(‟ Expr „)‟ {nl} Expr
While 循環表達式 while(e1)e2 的類型化與求值方式相似於函數 whileLoop (e1) (e2)的應用,假定的函數 whileLoop 定義以下:
def whileLoop(cond: => Boolean)(body: => Unit): Unit =
if (cond) { body ; whileLoop(cond)(body) } else {}
Expr1 ::= „do‟ Expr [semi] „while‟ „(‟ Expr „)‟
Do 循環表達式 do e1 while (e2) 的類型化與求值方式相似於表達式 (e1 ;
while (e2) e1)。Do 循環表達式中 while 前的分號被忽略。
Expr1 ::= „for‟ („(‟ Enumerators „)‟ | „{‟ Enumerators
„}‟) {nl} [„yield‟] Expr Enumerators ::= Generator {semi Enumerator} Enumerator ::= Generator
| Guard
| „val‟ Pattern1 „=‟ Expr Generator ::= Pattern1 „<-‟ Expr [Guard]
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 for 語句段
for (p <- e; p‟ <- e‟ ...) e‟‟
這裏... 是一個產生器或守衛序列(可能爲空),該語句段翻譯爲
e.foreach { case p => for (p‟ <- e‟ ...) e‟‟ }
l 後跟守衛 ifg 的 產 生 器 p <- e 翻 譯 爲 單 個 產 生 器 p <- e.filter((x1,...,xn) => g),這裏 x1,...,xn 是p 的自由變量。
l 後跟值定義 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)
}
以上代碼使用了類 scala.Array 中已有定義的成員 map, flatMap, filter 和
foreach。
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 異常。
Expr1 ::= „throw‟ Expr
throw 表達式 throw e 對錶達式 e 求值。該表達式的類型必須與 Throwable 一致。若是 e 求值的結果是異常的引用,則求值結束,拋出該異常。若是 e 求值的結果是 null, 則求值結束並拋出 NullPointerException 。若是此處有一個活動的 try 表達式(§6.22),且要處理拋出的異常,則求值在該處理器中繼續進行;不然執行 throw 的線程將被終止。throw 表達式的類型是 scala.Nothing。
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 指望與類型
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 的簡寫。
Expr ::= (Bindings | Id | „_‟) „=>‟ Expr
ResultExpr ::= (Bindings | (Id | „_‟) „:‟ CompoundType) „=>‟ Block Bindings ::= „(‟ Binding {„,‟ Binding} „)‟
Binding ::= (id | „_‟) [„:‟ Type]
匿名函數(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
//並返回新的值
_ => 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 x => x + 1
_ * _ (x1, x2) => x1 * x2
(_: Int) *2 (x: Int) => (x: Int) * 2
if (_) x else y z => if (z) x else y
_.map(f) x => x.map(f)
_.map(_ + 1) x => x.map(y => y + 1)
BlockStat ::= Import
| [„implicit‟] Def
| {LocalModifier} TmplDef
| Expr1
| TemplateStat ::= Import
| {Annotation} {Modifier} Def
| {Annotation} {Modifier} Del
| Expr
|
語句是代碼塊和模板的一部分。語句能夠是 import,定義或表達式,也可爲空。在模板或類定義中使用的語句還能夠是聲明。做爲語句來使用的表達式能夠有任意的值類型。表達式語句e 的求值是對錶達式 e 求值而後丟棄求值的結果。
代碼塊語句能夠是在代碼塊中綁定本地命名的定義。代碼塊本地定義中容許的修飾符是類或對象定義前的 abstract, final, sealed。
語句序列的求值是語句按照它們書寫順序的求值。
隱式轉換能夠應用於那些類型與指望類型不一致的表達式或未應用的方法。在接下來的兩小節中將給出可用的隱式轉換。
若是 T 與 U 在應用 eta 擴展(§6.25.5)和視圖應用(§7.3)後一致,則 T 與 U 是兼容的。
如下的五個隱式轉換能夠應用到具備類型 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 轉換爲指望的類型。
如下四個隱式轉換可應用到那些沒法應用到指定參數列表的方法上面。
求值 具備類型=> T 的無參數方法m 老是經過對m 綁定的表達式求值來轉換到類型 T
隱式應用 若是該方法只接受隱式參數,則將經過規則§7.2 傳遞隱式參量。
eta 擴展 不然,若是方法不是一個構造器,且指望類型是一個函數類型(Ts‟)=>T‟,則
eta-擴展應用於表達式e。
空應用 不然,若是e 擁有方法類型()T,則將隱式應用於空參數列表,產生 e()。
若是一個標識符或選擇 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)指向第二個。假設咱們添加第三個重載定義
def f(x: B, y: A) = ...
則應用 f(a, a)將因模糊定義而被拒絕,由於不存在更具體的可應用簽名。
本地類型推斷推測將要傳遞給多態類型的表達式的類型參數。好比 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
對於給定類型變量,可能不存在最小解或最大解,這將致使編譯時錯誤。因爲<:是前
序的,所以一個類型的解集中能夠有多個最優解。在此狀況下 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
因此 scala.Any 就是 a 的類型推斷的結果。
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)的方法。
LocalModifier ::= „implicit‟
ParamClauses ::= {ParamClause} [nl] „(‟ „implicit‟ Params „)‟
用 implicit 修飾符標記的模板成員和參數能夠傳遞給隱含參數(§7.2),且能夠在隱式轉換中使用,這種狀況稱爲視圖(§7.3)。implicit 修飾符不能用於全部的類型成員和頂級對象(§9.2)。
示例 7.1.1 如下代碼定義了一個幺半羣的抽象類以及兩個實現,StringMonoid 和
IntMonoid。這兩個實現標記爲 implicit
abstract class Monoid[A] extends SemiGroup[A] {
def unit: A
def add(x: A, y: A): A
}
object Monoids {
implicit object stringMonoid extends Monoid[String] { def add(x: String, y: String): String = x.concat(y) def unit: String = 「」;
}
implicit object intMonoid extends Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
}
隱含參數列表(implicit p1,...,pn)將參數 p1,...,pn 標記爲隱含的。一個方法或構造器僅能有一個隱含參數列表,且必須是給出的參數列表的最後一個。
具備隱含參數列表的方法能夠像正常方法同樣應用到參量上。這種狀況下 implicit
標識符沒有做用。然而若是該方法沒有隱含參數列表中的參量,對應的參量會自動提供。有兩種狀況實體參量能夠傳遞給類型爲 T 隱含參數。首先,全部的標識符 x 能夠在方
法被調用的地方無需前綴就能夠訪問到,且該標識符表示一個隱含定義(§7.1)或隱含參數。一個可用的標識符能夠是一個本地命名,或封閉模板的一個成員,或者經過 import
子句(§4.7)使其不用前綴便可訪問。其次,在隱含參數的類型 T 的隱含做用域中的對象的 implicit 成員也是可用的。
類型 T 的隱含做用域由與隱含參數的類型相關聯的類的全部伴隨模塊(§5.4)構成。類 C 與類型 T 相關聯的條件是它是T 的某部件的基類(§5.1.2)。類型 T 的部件指:
l 若是 T 是一個複合類型 T1 with ... with Tn,則是 T1,...,Tn 的部件以及
T 自身的合集。
l 若是 T 是一個參數化的類型 S[T1,...,Tn],則是 S 的部件以及 T1,...,Tn 的合集。
l 若是T 是一個單例類型p.type,則是 p 的類型的部件
l 若是T 是一個類型投影 S#U,則是 S 的部件和 T 自身
l 其餘狀況下則只是T 自身
若是有多個可用的參量與隱含參數的類型匹配,則將使用靜態重載解析(§6.25.3)來選擇一個最具體的。
示例 7.2.1 仍是示例 7.1.1 中的那些類,如今有一個方法用幺半羣的 add 和 unit 操做來計算一個元素列表的和。
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if(xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))
這裏的幺半羣被標記爲一個隱含參數,且可經過列表的類型來推斷。考慮調用
sum(List(1,2,3))
在上下文中 stringMonoid 和 intMonoid 都是可見的。咱們知道 sum 的正式類型參數 a 須要初始化爲 Int。與隱含正式參數類型 Monoid[Int]匹配的惟一可用對象是intMonoid,因此該對象會做爲隱含參數被傳遞。
這裏一樣說明了隱含參數在全部類型參量被推斷(§6.25.4)以後才被推斷。
隱含方法自身一樣能夠有隱含參數。如下是模塊 scala.List 中的一個例子,這裏將列表注入到scala.Ordered 類中,列表的元素類型也能夠轉化爲這裏的類型。
implicit def list2ordered[A](x: List[A])
(implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
...
假設再增長一個方法
implicit def int2ordered(x: Int): Ordered[Int]
該方法將整數注入到 Ordered 類中。如今咱們能夠在 ordered 列表上定義一個
sort 方法:
def sort[A](xs: List[A])(implicit a2ordered: A => Ordered[A]) = ...
如下咱們將方法sort 應用到整數列表的列表 yss: List[List[Int]]上:
sort(yss)
以上的調用將經過傳遞兩個嵌套的隱含參量完成:
sort(yss)(xs: List[Int] => list2ordered[Int](xs)(int2ordered))
將隱含參量傳遞給隱含參量將有可能致使死循環。好比,試圖定義如下方法,將任何類型都注入到 Ordered 類中:
implicit def magix[A](x: A)(implicit a2ordered: A => Ordered[A]): Ordered[A]): Ordered[A] = a2ordered(x)
如今,若是嘗試將 sort 應用於沒有另外注入到 Ordered 類中的參量 arg,則將獲得無限擴展:
sort(arg)(x => magic(x)(x => magic(x)(x => ... )))
爲了不這類無限擴展發生,編譯器將爲當前搜索的隱含參量建立一個「開放隱含類型」堆棧。當一個類型 T 的隱含參量被搜索時,T 的「核心類型」將會被添加到堆棧中。這裏 T 的核心類型是別名已展開,移除了頂級類型標註(§11)和修飾(§3.2.7),且將頂級存在邊界變量用他們的上界替換後的 T。在對隱含參數的搜索完成後,無論搜索成功與否,核心類型都會被從堆棧中刪除。每當有一個核心類型添加到堆棧中,將會檢查該類型沒有影響到集合中的任何其餘類型。
這裏一個核心類型 T 影響到一個類型 U 的狀況是 T 等價於 U,或 T 和 U 的頂級類型構造器有共有元素且T 比U 更復雜。
類型T 的頂級類型構造器集合 ttcs(T)與類型的形式有關: 對於類型指示器,
ttcs(p.c) = {c}
對於參數化類型,
ttcs(p.c[targs]) = {c}
對於單例類型,
ttcs(p.type) = ttcss(T),p 的類型是 T
對於複合類型,
ttcs(T1 with ... with Tn) = ttcs(T1) U ... U ttcs(Tn)
核心類型的複雜度 complexity(T)是一個依賴於類型的形式的整數: 對於類型指示器,
complexity(p.c) = 1 + complexity(p)
對於參數化類型,
complexity(p.c[targs]) = 1 + Σcomplexity(targs)
對於表示包 p 的單例類型,
complexity(p.type) = 0
對於其餘單例類型,
complexity(p.type) = 1 + complexity(T),p 的類型是 T
對於複合類型,
complexity(T1 with ... with Tn) = Σcomplexity(Ti)
示例 7.2.2 對於某些類型爲 List[List[List[Int]]]的列表 xs,sort(xs)類型化的隱含參量類型搜索序列是
List[List[Int]] => Ordered[List[List[Int]]], List[Int] => Ordered[List[Int]]
Int => Ordered[Int]
全部的類型都共享類型構造器 scala.Function1,可是每一個新類型的複雜度要比以前的類型低。這就是代碼的類型檢查方式。
示例 7.2.3 設ys 是某些不能轉變爲 Ordered 的類型的 List,例如:
val ys = List(new IllegalArgumentException, new ClassCastException, new
Error)
假設上下文中有以上定義的 magic。則隱含參量類型搜索的序列是
Throwable => Ordered[Throwable], Throwable => Ordered[Throwable],
...
因爲序列中的第二個類型等價於第一個,編譯器將產生一個發散隱含擴展錯誤。
隱含參數和方法也能夠定義隱式轉換,稱做視圖。由類型 S 到類型 T 的視圖由一個函數類型爲 S=>T 或(=>S)=>T 的隱含值或一個能夠轉變爲該類型的值定義。
視圖在兩種狀況下應用。
對於隱含參數,若是有多個可選者,則應用重載解析。
示例 7.3.1 類 scala.Ordered[A]有一個方法
def <= [B >: A](that: B)(implicit b2ordered: B => Ordered[B]): Boolean
設有類型爲 List[Int] 的 兩 個 列 表 xs 和 ys ,且 §7.2 中 定 義 的 方 法
list2ordered 和 int2ordered 在做用域中,那麼操做
xs <= ys
是合法的,且擴展爲
list2ordered(xs)(int2ordered).<= (ys)
(xs => list2ordered(xs)(int2ordered))
list2ordered 的第一個應用將列表 xs 轉變爲類 Ordered 的一個實例,第二個則是傳遞給<=方法的隱含參數的一部分。
TypeParam ::= (id | „_‟) [TypeParamClause][„>:‟ Type] [„<:‟ Type]
一個方法或非特徵類的類型參數 A 能夠有一個視圖邊界 A <% T。這裏的類型參數能夠實例化爲任何類型S 且S 可經過應用一個視圖變爲邊界 T。
處理包含這樣一個類型參數的方法或類等價於一個具備視圖參數的方法,如:
def f[A <% T](ps): R = ...
擴展爲
def f[A](ps)(implicit v: A => T): R = ...
v 是隱含參數的新名稱。特徵沒有構造器參數,不能應用該轉換。所以特徵的類型參數沒有視圖邊界。
示例 7.4.1 示例 7.3.1 中提到的<=方法具體聲明以下
def <= [B >: A <% Ordered[B]](that: B): Boolean
Pattern ::= Pattern1 { „|‟ Pattern1 } Pattern1 ::= varid „:‟ TypePat
| „_‟ „:‟ TypePat
| Pattern2
Pattern2 ::= varid [„@‟ Pattern3]
| Pattern3
Pattern3 ::= SimplePattern
| SimplePattern {id [nl] SimplePattern} SimplePattern ::= „_‟
| varid
| Literal
| StableId
| StableId „(‟ [Patterns [„,‟]] „)‟
| StableId „(‟[Patterns „,‟][varid „@‟] „_‟ „*‟ „)‟
| „(‟ [Patterns [„,‟]] „)‟
| XmlPattern
Patterns ::= Pattern {„,‟ Patterns}
模式由常量,構造器,變量和類型測試組成。模式匹配測試一個或一組值是否符合給定模式,若是符合則將模式中的變量綁定到該值或該組值的相關部分。一個模式中的同一個變量名不能被屢次綁定。
示例 8.1.1 一些模式的例子
xs 綁定到其他部分
模式匹配老是在能夠給出模式的指望類型的上下文中進行,且具備以下不一樣的類型:
8.1.1. 變量模式
SimplePattern ::= „_‟
| varid
變量模式 x 是一個簡單的標識符,第一個字母必須小寫,能夠匹配任何值,並綁定變量名到該值。x 的類型爲由外部給出的模式的指望類型,由該模式所處的外部表達式決定。變量模式的特殊狀況是通配符_,每次出現都被做爲一個新的變量使用。
Pattern1 ::= varid „:‟ TypePat
| „_‟ „:‟ TypePat
類型化模式 x:T 由模式變量 x 和類型模式 T 組成,匹配符合類型模式 T 的值(§8.2), 並綁定變量名到該值。
SimplePattern ::= Literal
字面值模式 L 匹配任何與字面值 L 相等(==)的值,L 的類型必須與模式的指望類型一致。
SimplePattern ::= StableId
穩定標識符模式爲一個穩定標識符 r(§3.1),r 的類型要與模式的指望類型一致,該模式匹配全部知足 r==v 的值v(§12.1)。
爲避免與變量模式的語法重疊,穩定標識符模式能夠不是小寫字母開始的簡單名字, 但同一變量名若被反引號引用,將被做爲穩定標識符模式處理。
示例 8.1.2 考慮如下函數定義:
def f(x: Int, y: Int) = x match {
case y => ...
}
在這裏,y 是一個變量模式,匹配任意的值(與 f 的參數 y 無關)。以下形式則能夠獲得穩定標識符模式:
def f(x: Int, y: Int) = x match {
case `y` => ...
}
如今模式匹配到函數 f 的參數y, 只有當f 的兩個參數x 和y 相等,匹配纔會成功。
SimplePattern ::= StableId '(' [Patterns [',']] ')'
構造器模式具備如 c(p1,...,pn)(n>=0) 的形式, 由穩定標識符 c 後跟模式p1,...,pn 組成。c 或者是簡單名字, 或者是一個限定的名字標識一個 case 類(§5.3.2)。若是 c 標識的 case 類是單態的,它必須與模式的指望類型一致,x 的主構造器的正式參數類型類型(§5.3)就做爲 p1,...,pn 的指望類型。若是該 case 類是多態的,其類型參數將被實例化,使得 c 的實例與模式的指望類型一致。構造器模式匹配全部由構造器調用 c(v1,...,vn)產生的對象,p1,...,pn 匹配到v1,...,vn。
有一種構造器模式的特殊狀況是 c 的正式參數類型結尾是一個重複參數,這將在後面
(§8.1.8)討論。
SimplePattern ::= '(' [Patterns [',']] ')'
一個元組模式(p1,...,pn)實際上是構造器模式 scala.Tuplen(p1,...,pn)的別名(n>=2),也能夠在末尾多加一個逗號:(p1,...,pn,)。空元組()是類型爲 scala.Unit 的惟一值。
SimplePattern ::= StableId '(' [Patterns [',']] ')'
提取模式具備與構造器模式相同的語法,但提取模式中的穩定標識符不是 case 類, 而是這樣一個對象,其擁有與模式相匹配的名爲 unapply 或者 unapplySeq 的方法。
一個對象 x 的 unapply 方法只接受單一參數而且符合如下任一條件時,咱們說它匹配模式 x(p1,...,pn):
若是對象 x 的 unapplySeq 方法只接受單一參數而且返回形如 Option[S]的值,這裏 S 是元素類型爲 T 的 Seq[T]的子類型,咱們說它匹配模式 x(p1,...,pn),將在(§8.1.8)中進一步討論。
SimplePattern ::= StableId '(' [Patterns ','] [varid '@'] '_' '*' ')'
模 式 序 列 p1,...,pn 在 兩 種 場 合 下 出 現 。 首先, 出如今 構 造 器 模式c(q1,...,qm,p1,...pn)中,其中 c 是一個 case 類,擁有 m+1 個主要構造參數,最後一個參數是類型爲 S*的重複參數(§4.6.2)。其次,出如今提取模式 x(p1,...,pn),如果對象 x 擁有一個返回 Seq[S] 的 unapplySeq 方法, 而沒有匹配 p1,...,pn 的unapply 方法。全部這兩種狀況,模式的指望類型都是S。
模式序列的最後一個模式能夠是序列通配符_*。每個模式 pi 要麼是指望類型 S,要麼是通配符。當最後一個模式是通配符時,整個模式序列匹配到長度>=n-1 的值序列,不然只能匹配與模式p1,...,pn 匹配的元素構成的長度爲n 的值序列。
Pattern3 ::= SimplePattern {id [nl] SimplePattern}
中綴操做符模式 p op q 是構造器模式或提取模式 op(p,q) 在語法上的簡化,操做符在模式中的優先級和結合性與在表達式中相同(§6.12)。
一樣, 中綴操做符模式 p op (q1,...,qn) 是 構造器 模 式 或 提 取 模 式
op(p,q1,...qn)的語法簡化。
Pattern ::= Pattern1 { '|' Pattern1 }
模式選擇 p1|...|pn 由一組備選模式 pi 組成,全部的備選模式在類型上都要符合模式的指望類型,它們不能綁定到除通配符外的任何變量。當至少一個備選模式匹配值 v, 則整個模式也匹配到v。
將在 §10.2 中討論。
正則表達式模式在 Scala2.0 之後再也不被支持,代之以一個大爲簡化的版本,該簡化
版本涵蓋非文本序列處理的大多數狀況。當一個模式符合下列條件之一,咱們稱其爲序列模式。
當一個模式 p 知足下列條件之一,咱們稱其爲類型T 的恆等模式
TypePat ::= Type
類型模式由類型、類型變量和通配符構成,具備如下可能的形式:
l 到類 C,類 p.C,或 T#C 的引用。匹配類 C 的全部非空實例。注意類的前綴(若是給出的話)是與決定類的實例無關的。例如模式 p.C(建立時有前綴路徑 p)只匹配類 C 的實例。
底層類型 scala.Nothing 和 scala.Null 不能用做類型模式,由於它們不能匹配任何東西。
l 單例類型 p.type,匹配路徑 p 表明的值(調用類 AnyRef 的 eq 方法判斷與 p 的同一性)
l 複合類型模式 T1 with ... with Tn,其中每一個 Ti 都是一個類型模式。匹配與每個Ti 都匹配的值。
l 參數化類型模式 T[a1,...,an],其中每一個 ai 或者是類型變量模式,或者是通配符_。匹配 a1,...,an 任意實例所產生的類型 T。類型變量的綁定或別名在(§8.3)討論。
l 參數化類型模式 scala.Array[T1],其中 T1 也是一個類型模式。該模式匹配
scala.Array[U1]的全部非空實例,條件是 T1 匹配類型 U1。
除了以上形式的類型,也存在其餘的類型模式,但其類型會變爲它們的類型擦除
(§3.7),致使 Scala 編譯器產生警告信息」unchecked」, 提示失去類型安全。
類型變量模式是一個小寫字符開頭的簡單標識符,不能採用 scala 預約義的基本類型名稱如 unit, boolean, byte, short, char, int, long, float 和 double。
類型參數推斷處理指定類型模式或構造器模式中的被綁定的類型變量的邊界,從而肯定模式的指望類型。
類型化模式的類型參數推斷:假定模式爲 p:T',將 T'中全部通配符用新的類型變量代替,
咱們獲得類型 T,其類型變量爲 a1,...,an,這些類型變量在模式中被綁定。並令模式的指望類型爲 pt。
推斷過程:首先爲類型變量 a1,...,an 構造一個子類型約束集合 C0。初始的約束集合反映了類型變量的邊界,假定 a1,...,an 相關於類類型參數 a1',...,an',且具備下界 L1,...,Ln 和上界U1,...,Un。集合C0 的約束條件以下:
ai <: σUi (i=1,...,n)
σLi <: ai (i=1,...,n)
σ 是替換[a1':=a1,...,an':=an]
針對集合C0 進一步進行子類型約束,這一過程可能有兩種狀況:
^ C2 使 T 與 pt 一致;若是 T 不是 final 類的實例類型,一樣有 a1,...,an 和 b1,...,bm 的最弱子類型約束集 C2,使得 C0 ^ C0' ^ C2 使有可能構造出類型 T'與 T 和 pt 都一致。若是沒有符合該條件的約束集C2,就會產生靜態錯誤。
最後一步,選擇類型參數的邊界以和前面創建的約束系統匹配,其處理過程因上一步的兩種不一樣狀況而異:
在兩種狀況下,都引入了本地類型推斷來下降總體邊界推斷的複雜性,類型的最小值和最大值使得類型集合的複雜度能夠接受。
構造器模式的類型參數推斷:假設構造器模式 C(p1,...,pn)中,類 C 具備類型參數a1,...,an,這些類型參數能夠用與類型化模式(_: C[a1,...,an])相同的方法推斷出來。
示例 8.3.1 考慮如下程序片斷:
val x: Any x match {
case y: List[a]=>...
}
這裏,類型模式 List[a]與指望類型 Any 進行匹配,模式綁定了類型變量 a, List[a]的任何類型參數都使得 List[a]與 Any 一致,所以 a 是沒有邊界的抽象類型。a 的做用域就是case 子句右邊的代碼。
另外一方面,若是x 的聲明是這樣的:
val x: List[List[String]]
這會產生約束 List[a] <: List[List[String]]。由於 List 類型具備協變性, 該約束可簡化爲 a <: List[String],這樣可知 a 具備類型上界 List[String]。
示例 8.3.2 考慮如下程序片斷:
val x: Any x match {
case y: List[String] => ...
}
在運行時,Scala 並不保存類型參量的信息,因此沒有辦法檢查 x 是不是字符串列表。編譯器將對模式 List[String]進行類型擦除(§3.7),成爲 List[_]。即只檢查 x 的 頂級運行時類是否與 List 一致,若是是,則模式將被匹配。這可能致使類型轉換的異常, 若是列表 x 的元素並不是字符串的話。Scala 編譯器會針對這種狀況給出「unchecked」 警告信息,提醒用戶潛在的類型安全缺陷。
示例 8.3.3 考慮如下程序片斷:
class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
case y: Number => y.n
}
模式 y: Number 的指望類型是 Term[B],但類型 Number 並不與 Term[B]一致, 所以必須使用前面討論過的規則 2,引入類型變量 b 來推斷子類型約束。在這個例子裏我們有約束 Number <: Term[B],限定了 B=Int。所以 B 在 case 語句被看成一個抽象類型,其上下限均爲 Int,由此,case 語句的右邊部分 y.n 的類型爲 Int,也就 與函數聲明的類型 Number 一致了。
Expr ::= PostfixExpr „match‟ „{‟ CaseClauses „}‟ CaseClauses ::= CaseClause {CaseClause}
CaseClause ::= „case‟ Pattern [Guard] „=>‟ Block
模式匹配表達式形爲
e match { case p1 => b1 . . . case pn => bn }
模式匹配表達式由一個選擇器表達式 e 和一組 n 個 case 表達式組成(n>0)。每一個case 由一個(可能有守衛的)模式 pi 和代碼塊 bi 組成。每一個模式 pi 能夠由守衛語句(if e>)進一步限定(這裏 e 爲布爾型表達式)。pi 中的模式變量的做用域包括守衛語句和對應的代碼塊 bi。
令選擇器表達式 e 的類型爲 T, 而且 a1,...am 是擁有模式匹配表達式內的方法的類型參數,每一個 ai 具備類型下界 Li 和類型上界 Ui。每一個模式 p( p ∈ {p1,...,pn} )的類型有兩種方法獲得。首先,試圖令 T 爲 p 的指望類型,若是失敗,將 T 的每一個類型參數ai 用 undefined 取代,獲得另外一個指望類型 T'。若是這一步也失敗的話,將會拋出一個編譯錯誤。不然令 Tp 爲表達式 p 的類型,決定一組類型下界 L1',...,Lm'和類型上界U1',...,Um'使得對全部i,關係Li<:Li'和Ui'<:Ui 成立,且知足下面的約束關係:
L1 <: a1 <: U1 ∧ . . . ∧ Lm <: am <: Um => Tp <: T
若是沒有這樣的類型邊界,將拋出一個編譯錯誤。不然 ai 的上下界將爲 Li'和 Ui', 由此獲得 p 開頭的模式匹配子句的類型。
每個代碼塊 bi 的指望類型就是整個模式匹配表達式的指望類型,而模式匹配表達式的實際類型是全部代碼塊bi 的類型的最小公共上界。
當把模式匹配表達式應用於選擇器時,選擇器按順序匹配全部模式,直到匹配成功。假設匹配到 case pi=>bi,整個表達式的結果就是代碼塊 bi 的求值結果。而 pi 所包含的全部模式變量將會綁定到選擇器的對應部分。 若是匹配失敗, 將會拋出一個scala.MatchError 異常。
case 表達式中的模式能夠有守衛後綴 if e,e 爲布爾型表達式。匹配到該模式以後, 對守衛表達式求值,值爲真則匹配成功,不然繼續匹配後繼模式。
爲了提升運行效率,編譯器有可能打亂 case 表達式所給定的模式匹配次序。這時若是某些模式的守衛表達式包含反作用的話,就可能會對結果產生影響,但編譯器可以確保只有模式被匹配到的時候,其守衛表達式才被求值。
若是選擇器是 sealed 類(§5.2)的實例,編譯器會給出警告,提醒給定待匹配的模式枚舉不徹底,運行時可能致使 scala.MatchError 異常。
示例 8.4.1 考慮如下算術運算符的定義:
abstract class Term[T]
case class Lit(x: Int) extends Term[Int]
case class Succ(t: Term[Int]) extends Term[Int]
case class IsZero(t: Term[Int]) extends Term[Boolean]
case class If[T](c: Term[Boolean],
t1: Term[T],
t2: Term[T]) extends Term[T]
以上定義包括數字字面值,加 1 運算,0 值測試和條件運算。每一個運算符都有一個類型參數,代表運算的類型(整型或布爾型)。
下面是關於上述運算的類型安全的求值函數:
def eval[T](t: Term[T]): T = t match {
case Lit(n) => n
case Succ(u) => eval(u) + 1
case IsZero(u) => eval(u) == 0
case If(c, u1, u2) => eval(if (eval(c)) u1 else u2)
}
類型參數能夠經過模式匹配得到新的類型邊界這一事實,是求值函數得以成立的關鍵。例如:第二個 case 中,模式 Succ(u)類型參數 T 的類型爲 Int,只有當 T 的類型
上下界都是 Int 時,才符合選擇器的指望類型。有了 Int <: T <: Int 這個假定,我們能夠驗證第二個的右側的類型 Int 與指望類型 T 一致。
BlockExpr ::= '{' CaseClauses '}'
匿名函數由一系列 case 組成:
{ case p1 => b1 . . . case pn => bn }
其實是一個沒有前綴 match 的表達式。表達式的指望類型必須部分被定義,要麼是
scala.Functionk[S1,...,Sk, R](k>0),要麼是 scala.PartialFunction[S1,
R],其中參數類型S1,...,Sk 必須明確,而結果類型能夠待定。
若是指望類型是 scala.Functionk[S1,...,Sk, R],則整個表達式與下面的匿名函數等價:
(x1:S1,...,xk:Sk ) => (x1,...,xk) match {
case p1 => b1 ... case pn => bn
}
xi 是新引入的變量名。如(§6.23)所示,上述匿名函數與下面的實例構造表達式等價
(這裏的T 是全部bi 的最小共同類型上界)。
new scala.Functionk[S1,...,Sk, T] {
def apply(x1 : S1,...,xk : Sk ): T = (x1,...,xk) match {
case p1 => b1 ... case pn => bn
}
}
若是指望類型是 scala.PartialFunction[S, R],表達式與下列實例構造表達式等價:
new scala.PartialFunction[S , T ] {
def apply(x : S): T = x match {
case p1 => b1 ... case pn => bn
}
def isDefinedAt(x : S): Boolean = { case p1 => true ... case pn => true case _ => false
}
}
x 是新引入的變量名,T 爲全部 bi 的最小共同類型上界。isDefinedAt 方法中,若是前面的模式p1,...pn 已經有了變量模式或通配符模式,最後的缺省case 將被忽略。示例 8.5.1 這個函數使用 fold-left 操做符/:計算兩個矢量的標積:
def scalarProduct(xs: Array[Double], ys: Array[Double]) = (0.0 /: (xs zip ys)) {
case (a, (b, c)) => a + b * c
}
case 子句與下列匿名函數等價:
(x, y) => (x, y) match {
case (a, (b, c)) => a + b * c
}
CompilationUnit ::= [„package‟ QualId semi] TopStatSeq TopStatSeq ::= TopStat {semi TopStat}
TopStat ::= {Annotation} {Modifier} TmplDef
| Import
| Packaging
|
QualId ::= id {„.‟ id}
編譯單元由包,導入子句和類與對象定義(前面能夠是包子句)構成。
有包子句開始的編譯單元 package p; stats 等價於由單個包 package p
{stats}構成的編譯單元。
按照順序隱式導入每一個編譯單元的內容有:包 java.lang,包 scala 以及對象
scala.Predef(§12.5)。該順序中後引入的成員將隱藏先前引入的成員。
Packaging ::= package QualId [nl] „{‟ TopStatSeq „}‟
包是一個定義了一些成員類,對象與包的特殊對象。與其餘對象不一樣,包不是由定義引入的。包的成員集合是由打包肯定的。
打包 package p { ds }將 ds 中全部的定義做爲成員放到限定名爲 p 的包中。包的成員稱爲頂級定義。若是 ds 中某定義標記爲 private,則僅在包內成員中可見。
p 中或從 p 導入的選擇 p.m 工做方式相似對象。然而不像其餘對象,包不能用做值。包與模塊或對象不能有相同的完整的限定名。
打包以外的頂級定義默認放到一個特別的空包中。這個包不能被命名,也不能被導入。然而空包的成員不用限定就對彼此可見。
QualId ::= id {„.‟ id}
到包的引用具備限定標識符的形式。和其餘的引用相似,包的引用是相對的。也就是說,由命名p 開始的包的引用將在定義了名爲 p 的成員的最近的封閉域內查找。
特例是預約義的命名_root_,指最外層的根包,幷包括全部頂層的包。
示例 9.3.1 考慮如下程序:
package b {
class B
}
package a.b {
class A {
val x = new _root_.b.B
}
}
這裏引用_root_.b.B 指頂級包 b 中的類 B。若是忽略_root_前綴,命名 b 就解析爲包 a.b,因爲該包中沒有名爲 B 的類,因此這樣會致使編譯錯誤。
程序是頂級對象,具備一個類型爲(Array[String])Unit 的方法成員 main。程序能夠在命令行界面中執行。程序的命令行參量將以類型 Array[String]的形式傳遞給main 方法。
程序的 main 方法能夠直接在對象中定義,也能夠繼承。scala 庫中定義了一個類, 叫作 scala.Application,定義了一個空的繼承的 main 方法。繼承自該類的對象 m 就是一個程序,會執行對象m 的初始化代碼。
示例 9.4.1 如下示例將經過在模塊 test.HelloWorld 中定義一個 main 方法來建立一個 hello world 程序。
package test
object HelloWorld {
def main(args: Array[String]) { println(「hello world」) }
}
這個程序能夠用如下命令來啓動
scala test.HelloWorld
在 Java 環境中,能夠用如下命令
java test.HelloWorld
HelloWorld 也能夠繼承 Application,無需定義 main 方法:
package test
object HelloWorld extends Application { println(「Hello world」)
}
做者:Burak Emir
在這一章裏描述了 XML 表達式與模式的語法結構。該結構儘可能遵循 XML 1.0 規範
[W3C],只是爲了嵌入 Scala 代碼片斷而作了一些修改。
XML 表達式是由如下方法產生的表達式,第一個元素的左尖括號‟<‟必需要在一個開始 XML 詞法模式的位置(§1.5)。
XmlExpr ::= XmlContent {Element}
要符合 XML 規範的良好形式,好比開始標籤和結束標籤必須匹配,屬性只定義一次, 除非與實體解析有關的限制。
如下描述了 Scala 的可擴展標記語言,從設計上儘量的與 W3C 的可擴展標記語言標準接近。只有在屬性值和字符數據上有所改變。Scala 不支持聲明,CDATA 段或處理指令。實體引用並不在運行時解析。
Element ::= EmptyElemTag | Stag Content ETag EmptyElemTat ::= „<‟ Name {S Attribute} [S] „/>‟ Stag ::= „<‟ Name {S Attribute} [S] „>‟
ETag ::= „</‟ Name [S] „>‟
Content ::= XmlContent | Reference | ScalaExpr XmlContent ::= Element | CDSect | PI | Comment
若是 XML 表達式是一個單個元素,則其值是一個 XML 節點(scala.xml.Node 的子類的實例)的運行時表示。若是 XML 表達式包括一個以上的元素,則其值是一個 XML 節點的序列(scala.Seq[scala.xml.Node]的子類的實例)的運行時表示。
若是 XML 表達式是一個實體引用,CDATA 段,處理指令或標註,則由對應的 Scala
運行時類的實例來表示。
元素內容中首尾空格默認被刪除,連續空白字符用單個空白字符\u0020 替換。可經過用一個編譯選項來保留全部空白字符來改變該行爲。
Attribute ::= Name Eq AttValue
AttValue ::= „」‟ {CharQ | CharRef} „」‟
| „‟‟ {CharA | CharRef} „‟‟
| ScalaExpr
ScalaExpr ::= Block
CharData ::= {CharNoRef} without {CharNoRef} „{‟CharB
{CharNoRef} and without {CharNoRef}‟]]>‟ {CharNoRef}
XML 表達式能夠包含 Scala 表達式做爲屬性值或在節點內。在後者它們以左大括號
„{‟開始右大括號„}‟結束的方式嵌入。在 CharData 產生的 XML 文本中表示單個左大括號„{‟則須要將其重複一次。即„{{‟表示 XML 文本„{‟,並不會引入嵌入的 Scala 表達式。
BaseChar, Char, Comment, CombiningChar, Ideorgraphic, NameChar, S, Reference ::= 「和 W3C XML 同樣」
Char1 ::= Char without „<‟ | „&‟
CharQ ::= Char1 without „」‟
CharA ::= Char1 without „‟‟
CharB ::= Char1 without „{‟
Name ::= XNameStart {NameChar}
XNameStart ::= „_‟ | BaseChar | Ideographic (和 W3C XML 同樣,可是沒有 „:‟)
XML 模式是由如下方式產生的模式,元素模式的左尖括號„<‟必須在開始 XML 模式詞法的起始位置(§1.5)。
XmlPattern ::= ElementPattern
此處應用 XML 規範的良好格式限制。
一個 XML 模式必須是單個元素模式。它必須準確匹配具備模式描述的一樣結構的 XML
樹的運行時表現。XML 模式能夠包含Scala 模式(§8.4)。
空格的處理與 XML 表達式中的同樣。實體引用,CDATA 段,處理指令和標註的模式的運行時表示也同樣。
元素中首尾空格默認被刪除,連續空白字符用單個空白字符\u0020 替換。該行爲可經過用一個編譯選項來保留全部空白字符來改變。
ElemPattern ::= EmptyElemTagP | STagP ContentP ETagP EmptyElemTagP ::= „<‟ Name [S] „/>‟
STagP ::= „<‟ Name [S] „>‟
ETagP ::= „</‟ Name [S]„>‟
ContentP ::= [CharData] {(ElemPattern | ScalaPatterns) [CharData]} ContentP1 ::= ElemPattern
| Reference
| CDSect
| PI
| Comment
| ScalaPatterns ScalaPatterns ::= „{‟ Patterns „}‟
Annotation ::= „@‟ AnnotationExpr [nl] AnnotationExpr ::= Constr [[nl] „{‟ [NameValuePair
{„,‟ NameValuePair}] „}‟]
NameValuePair ::= val id „=‟ PrefixExpr
用戶定義的標註將元信息與定義關聯起來。一個簡單的標註具備@c 或@c(a1,...,an) 的形式。這裏c 是類 C 的構造器,且該類必須與類 scala.Annotation 一致。構造器後可跟括號中可選的名/值對列表,例如{n1=c1,...,nk=ck}。該列表中的全部的值ci 必須爲常量表達式,定義以下所示。
標註能夠應用在定義或聲明,類型,或表達式上。定義或聲明的標註放在該定義的前面。類型的標註放在類型的後面。表達式的標註在表達式以後,中間有一個冒號。對一個實體能夠應用多個標註。標註給出的順序並無意義。
示例:
@serializable class C { ... } //類標註@transient @ volatile var m: Int //變量標註String @local //類型標註
(e: @unchecked) match { ... } //表達式標註
標註的含義依賴於其實現。在 Java 平臺上,如下標註具備標準意義。
@transient
將一個字段標記爲不保存;等價於 Java 中的 transient 修飾符
@volatile
將一個字段標記爲可在程序控制外改變其值;等價於 Java 中的 volatile 修飾
符
@serializable
將一個類標記爲可序列化的;等價於在 Java 中繼承接口
java.io.Serializable
@SerialVersionUID(<longlit>)
給一個類加上序列化版本標識符(一個長整型常量)。等價於 Java 中的如下字段定義:
private final static SerialVersionUID = <longlit>
@throws(<classlit>)
Java 編譯器會經過分析一個方法或構造器是否會致使已檢查異常來檢查程序是否包含已檢查異常的處理器。對於每一個可能的已檢查異常,方法或構造器的 throws 子句必需要說明異常的類或異常的某個父類。
@deprecated
將一個定義標記爲不推薦的。對定義實體的訪問將致使編譯器發出一個不推薦警告。若是該代碼自身屬於一個標記爲不推薦的定義,則該警告會被抑制。
@scala.reflect.BeanProperty
看成爲一個定義或某變量X 的前綴時,該標註將使 Java bean 形式的 getter 和 setter 方法 getX,setX 添加到該變量所在的類中。get 或 set 後變量的第一個字母將變爲大寫。當該標註加在一個不可變值定義 X 上時則只產生getter。這些方法的構建是代碼生成的一部分;所以,這些方法只有在對應的 class 文件生成後纔可見。
@unchecked
當應用於一個 match 表達式的選擇器時,該屬性將抑制全部有關不徹底的模式匹配的警告,這些匹配將會被忽略。好比如下方法定義不會產生警告。
def f(x: Option[Int]) = (x: @unchecked) match {
case Some(y) => y
}
若是沒有@unchecked 標註,Scala 編譯器就會指出模式匹配是不徹底的,併產生一個警告(因爲 Option 是一個 sealed 類)。
@uncheckedStable
當應用於一個值聲明或定義時,則容許定義的值出如今一個路徑中,即便其類型是易變的。例如,如下定義是合法的:
type A { type T }
@uncheckedStable val x: A with B // 易變類型
val y: x.T // OK,由於‟x‟仍是一個路
徑
若是沒有該標註,指示器 x 就不能是一個路徑,由於其類型 A with B 是易變的。
繼而引用 x.T 是格式錯誤的。
當應用於非可變類型的值聲明或定義時,該標註沒有做用。
其餘標註將由平臺或應用有關的工具解釋。類 scala.Annotation 有兩個子特徵, 用來表示這些標註是如何被持有的。繼承自特徵 scala.ClassfileAnnotation 的標註類的實例將被保存在生成的類文件中。繼承自特徵 scala.StaticAnnotation 的標註類的實例將在每一個被標註的符號被訪問的編譯單元中,且對 Scala 類型檢查器可見。標註類也可同時繼承scala.ClassfileAnnotation 和 scala.StaticAnnotation。如
果一個標註類沒有繼承 scala.ClassfileAnnotation 也沒有繼承
scala.StaticAnnotation,則其實例僅在編譯器檢查它們時在本地可見。
繼承自 scala.ClassfileAnnotation 的類可能有更多的限制,這是爲了保證它們能夠映射到宿主環境中。特別是在 Java 和.NET 平臺上,這些類必須是頂級的;好比他們不能包含在其餘類或對象中。且在 Java 和.NET 中,全部的構造器參量都必須是常量表達式。
「常量表達式」的定義是與平臺有關的,可是必須包含如下形式的表達式:
l 爲值類的字面值,好比整數
l 字符串字面值
l 由 classOf 構建的類
l 平臺上的某枚舉類型的元素
l 字面值數組,形式爲@Array(c1,...,cn),全部的ci 都是常量表達式。
Scala 標準庫包括包 scala 和一些類與模塊。下面描述了這些類的一部分。
圖 12 展現了 Scala 類的層次結構。層次結構的根是類 Any。Scala 執行環境中的每個類都直接或間接地繼承自該類。類 Any 有兩個直接子類:AnyRef 和 AnyVal。
子類 AnyRef 表示在宿主系統中表示爲一個對象的全部值。全部用戶定義的 Scala 類都直接或間接的繼承自該類。更進一步,全部用戶定義的 Scala 類也都繼承自特徵scala.ScalaObject。由其餘語言編寫的類也都繼承自 scala.AnyRef,可是並無繼承 scala.ScalaObject。
圖 12.1 Scala 類層次結構
類 AnyVal 有固定數目的子類,它們描述了在宿主系統中沒有做爲對象來實現的那些值。
類 AnyRef 和 AnyVal 只須要提供在類 Any 中聲明的成員,可是具體實現能夠添加宿主有關的方法到這些類中(例如,一個實現能夠有其自有的對象的根類)。
這些根類的簽名描述以下。
package scala
/** 通用根類 */
abstract class Any {
/** 定義相等,在這裏是抽象的 */
def equals(that: Any): Boolean
/** 值間的語義相等 */
final def == (that: Any): Boolean =
if (null eq this) null eq that else this equals that
/** 值間的語義不等 */
final def != (that: Any): Boolean = !(this == that)
/** Hash code, 這裏是抽象的 */
def hashCode: Int = ...
/** 文本表示,這裏是抽象的 */
def toString: String = ...
/** 類型測試,須要按照下面的方式內聯化 */
def isInstanceOf[a]: Boolean = this match {
case x: a => true case _ => false
}
/** 類型轉換,須要按照下面的方式內聯化 */
def asInstanceOf[A] = this match {
case x: A => x
case _ => if (this eq null) this
else throw new ClassCastException()
}
}
/** 全部值類型的根類 */
final class AnyVal extends Any
/** 全部引用類型的根類 */
class AnyRef extends Any {
def equals(that: Any): Boolean = this eq that
final def eq(that: AnyRef): Boolean = ... //引用相等
final def ne(that: AnyRef): Boolean = !(this eq that)
def hashCode: Int = ... //由內存地址計算得來
def toString: String = ... //由 hashcode 和類名得來
}
/** 應用於用戶定義的 scala 類的混入類 */
trait ScalaObject extends AnyRef
對於測試 x.asInstanceOf[T],若是 T 是一個數字值類型(§12.2),則作特殊處理。在此狀況下的轉換是應用一個轉換方法 x.toT(§12.2.1)。對於不是數值的值 x 該操做將致使ClassCastException。
值類指在宿主系統中沒有表現爲對象的那些類。全部的值類都繼承自 AnyVal。Scala 實現須要提供值類 Unit, Boolean, Double, Float, Long, Int, Char, Short 和 Byte(也能夠提供更多的類)。這些類的特色定義以下。
類 Double, Float, Long, Int, Char, Short 和 Byte 統稱爲數字值類型。類 Byte,Short 和 Char 稱爲子界類型。還有如下子界類型,Int 和 Long 稱爲整數類型, Float 和 Double 稱爲浮點類型。
數字值類型的級別爲如下偏序:
Byte – Short
\
Int – Long – Float – Double
/
Char
在此排序中,Byte 和 Short 的級別最低,Double 級別最高。級別並非指一致性(§3.5.2)關係;例如 Int 不是 Long 的子類型。然而,對象 Predef(§12.5)定義了從每一個數字值類型到其高級數字值類型的視圖(§7.3)。所以,若是在上下文(§6.25)中有須要,低級的類型能夠隱式的轉換爲高級的類型。
給定兩個數字值類型 S 和 T,它們的操做類型定義以下:若是 S 和 T 都是子界類型則S 和 T 的操做類型是 Int。不然 S 和 T 的操做類型就是相對排名中較大的一個。給定兩個數字值類型v 和 w,它們的操做類型就是它們運行時的類型。
任何數字值類型T 都支持如下方法。
l 比較方法,好比等於(==),不等(!=),小於(<),大於(>),小於等於(<=), 大於等於(>=),每一個方法都存在 7 個重載的可選項。每一個可選項有一個數字值類型的參數。結果類型爲 Boolean。操做形式爲將接受者和參量變爲其操做類型, 並在此類型上作比較操做。
l 算數運算加(+),減(-),乘(*),除(/)和求餘(%),每一個方法都存在 7 個重載的可選項。每一個可選項有一個類型爲 U 的數字值類型的參數。結果類型是 T 和 U 的操做類型。操做形式爲將接受者和參量變爲其操做類型,並在此類型上作算數操做。
l 無參數算數方法正(+)和(-)的結果類型爲 T。第一個返回其自身,第二個返回其
負值。
l 轉 換 方 法 toByte, toShort, toChar, toInt, toLong, toFloat, toDouble 將對應的對象變爲目標類型,規則是 Java 的數值類型轉換操做。轉換可能截取數字值(好比從 Long 到 Int 或從 Int 到 Byte)或丟失精度(好比從Double 到 Float 或 Long 和 Float 之間的轉換)。
整型數字值也支持如下的操做:
l 位操做方法按位與(&),按位或(|)和按位異或(^),每一個方法都存在 5 個重載的可選項。每一個可選項都有一個整數值類型的參數。結果類型是 T 和 U 的操做類型。操做形式爲將接受者和參量變爲其操做類型,並在此類型上作對應的位運算操做。
l 無參數方法按位取反(~)。結果類型是接受者的類型 T 或 Int 中的較大者。操做方式是將接受者變爲結果類型並將按位取反。
l 移位操做 包括左移(<<),算數右移(>>)和無符號右移(>>>)。每一個方法有兩個重載的可選項,並有一個類型爲 Int 或 Long 的參數 n。操做的結果類型就是接受者的類型 T 或 Int 中較大者。操做形式爲將接受者變爲結果類型並移動n 位。
數字值類型一樣也實現了類 Any 中的 equals, hashCode 和 toString 操做。
equals 方法測試參量是否是一個數字值類型。若是爲 true 的話則執行對應類型的
==操做。一個數值類型的等於方法能夠認爲定義以下:
def equals(other: Any): Boolean = other match {
case that: Byte => this == that case that: Short => this == that case that: Char => this == that case that: Int => this == that case that: Long => this == that case that: Float => this == that case that: Double => this == that case _ => false
}
hashCode 方法返回一個 hashcode 整數,對於相等的結果返回相等的數字值。對於
Int 類型和其餘子界類型這一點必須獲得保證。
接受者的 toString 方法將顯示爲一個整數或浮點數。
示例 12.2.1 數字值類型 Int 的簽名以下所示:
package scala
abstract sealed class Int extends AnyVal {
def |
== |
(that: |
Double): Boolean |
// |
double 相等 |
def |
== |
(that: |
Float): Boolean |
// |
float 相等 |
def |
== |
(that: |
Long): Boolean |
// |
long 相等 |
def |
== |
(that: |
Int): Boolean |
// |
int 相等 |
def |
== |
(that: |
Short): Boolean |
// |
int 相等 |
def |
== |
(that: |
Byte): Boolean |
// |
int 相等 |
def |
== |
(that: |
Char): Boolean |
// |
int 相等 |
/* !=, <, >, <=, >=的狀況相似 */
def |
+ |
(that: |
Double): Double |
// |
double 加 |
def |
+ |
(that: |
Float): Double |
// |
float 加 |
def |
+ |
(that: |
Long): Long |
// |
long 加 |
def |
+ |
(that: |
Int): Int |
// |
int 加 |
def |
+ |
(that: |
Short): Int |
// |
int 加 |
def |
+ |
(that: |
Byte): Int |
// |
int 加 |
def |
+ |
(that: |
Char): Int |
// |
int 加 |
/* -, *, /, %的狀況相似 */
def & (that: Long): Long // long 按位與def & (that: Int): Int // int 按位與def & (that: Short): Int // int 按位與def & (that: Byte): Int // int 按位與
def & (that: Char): Int // int 按位與
/* |, ^的狀況相似 */
def << (cnt: Int): Int // int 左移
def << (cnt: Long): Int // long 左移
/* >>, >>>的狀況相似 */
def unary_+ : Int // int 正
def unary_- : Int // int 負
def unary_~ : Int // int 按位取反
def toByte: Byte // 變爲 Byte
def toShort: Short // 變爲 Short
def toChar: Char // 變爲 Char
def toInt: Int // 變爲 Int
def toLong: Long // 變爲 Long
def toFloat: Float // 變爲 Float
def toDouble: Double // 變爲 Double
}
類 Boolean 只有兩個值: true 和 false,如下類定義給出了其實現的操做。
package scala
abstract sealed class Boolean extends AnyVal {
def && (p: => Boolean): Boolean = // 布爾與
if (this) p else false
def || (p: => Boolean): Boolean = // 布爾或
if (this) true else p
def & (x: Boolean): Boolean = // 布爾嚴格與
if (this) x else false
def | (x: Boolean): Boolean = // 布爾嚴格或
if (this) true else x
def == (x: Boolean): Boolean = // 布爾相等
if (this) x else x.unary_!
def != (x: Boolean): Boolean // 布爾不等
if (this) x.unary_! else x
def unary_!: Boolean // 布爾負
if(this) false else true
}
該類也實現了 Any 類中的 equals, hashCode 和 toString 操做。
equals 方法在參量與接受者是同一布爾值時返回 true,不然 false。hashCode 方法在 true 上調用時返回 1,在 false 上調用返回 0。toString 方法將接受者變爲一個字符串,好比」true」或者」false」。
類 Unit 只有一個值:()。且只實現了 Any 類中的三個方法 equals, hashCode 和
toString。
若是參量是值()則 equals 方法返回 true,不然 false。hashCode 方法返回一個固定的與實現有關的hash-code 值。toString 方法返回」()」。
本節描述了某些在 Scala 編譯器中作特殊處理的標準 Scala 引用類 – 要麼是Scala 給它們提供了一些語法糖,要麼 Scala 編譯器爲它們的操做產生了一些特殊代碼。Scala 標準庫中的其餘類的文檔在 Scala 庫文檔的 HTML 頁中。
Scala 的 String 類通常派生自宿主系統的 String 類(可能有所不一樣)。對於
Scala 客戶來講該類確定支持一個方法
def + (that: Any): String
該方法將左側的操做數與右側操做數的字符形式鏈接在一塊兒。
Scala 定義了元組類 Tuplen n=2,...,9。定義以下。
package scala
case class Tuplen[+a_1, ..., +a_n](_1: a_1, ..., _n: a_n) {
def toString = 「(」 ++_1 ++ 「,」 ++ ... ++ 「,」 ++ _n ++ 「)」
}
隱式引入的 Predef 對象(§12.5)定義了命名 Pair 做爲 Tuple2 的別名和 Triple
做爲 Tuple3 的別名。
Scala 定義了函數類 Functionn, n=1,...,9。定義以下。
package scala
trait Functionn[-a_1, ..., -a_n, +b] {
def apply(x_1: a_1, ..., x_n: a_n): b
def toString = 「<function>」
}
Function1 的子類表示一個部分函數,在某些狀況下在其領域中是沒有定義的。對於 apply 方法,部分函數還有一個 isDefined 方法,來講明函數對於給定的參數是否認義:
class PartialFunction[-A, +B] extends Function1[A, B] {
def isDefinedAt(x: A): Boolean
}
隱式引入的 Predef 對象(§12.5)定義了命名 Function 做爲 Function1 的別名。
通用數組類定義以下。
final class Array[A](len: Int) extends Seq[A] {
def length: Int = len
def apply(i: Int): A = ...
def update(i: Int, x: A): Unit = ...
def elements: Iterator[A] = ...
def subArray(from: Int, end: Int): Array[A] = ...
def filter(p: A => Boolean): Array[A] = ...
def map[B](f: A => B): Array[B] = ...
def flatMap[B](f: A => Array[B]): Array[B] = ...
}
若是 T 不是類型參數或抽象類型,類型 Array[T]表示宿主系統中的原生數組類型[]T。這種狀況下 length 返回數組的長度,apply 表示下標,update 表示元素更新。因爲 apply 和 update 操做(§6.25)的語法糖的存在,如下是 Scala 和 Java/C#中對數組xs 操做的對應:
Scala Java/C#
xs.length xs.length
xs(i) xs[i]
xs(i) = e xs[i] = e
數組也實現了序列特徵 scala.Seq,定義了 elements 方法來返回一個包含數組中全部元素的Iterator
由於在 Scala 中參數化類型的數組和宿主語言中數組的實現仍是有差別的,在處理數組時須要注意一些細小的差異。解釋以下。
首先,不像 Java 或 C#中的數組,Scala 中的數組不是協變的;也就是說,在Scala 中 S<:T 並不能得出 Array[S] <: Array[T]。可是若是在宿主環境中能夠將 S 變爲T 則能夠將 S 的數組變爲T 的數組。
舉例來講 Array[String]並不與 Array[Object]一致,即便 String 與 Object 一致。然而能夠將類型爲 Array[String]的表達式變爲 Array[Object]。該轉變將會成功,不會拋出ClassCastException。例子以下:
val xs = new Array[String](2)
// val ys: Array[Object] = xs // **** 錯誤:不兼容的類型
val ys: Array[Object] = xs.asInstanceOf[Array[Object]] //OK
其次,對於有一個類型參數或抽象類型 T 做爲元素類型的多態數組,其表現形式不一樣於[]T。然而 isInstanceOf 和 asInstanceOf 仍像數組數組使用單態數組的標準表現形式那樣正常工做。
val ss = new Array[String](2)
def f[T](xs: Array[T]): Array[String] = if(xs.isInstanceOf[Array[String]]) xs.asInstanceOf[Array[String]] else throw new Error(「not an instance」)
f(ss) // 返回 ss
多態數組的表現形式一樣保證了多態數組的建立與指望一致。如下是一個 mkArray
方法的例子,給定定義了元素且類型爲T 的序列建立任意類型 T 的數組。
def mkArray[T](elems: Seq[T]): Array[T] = { val result = new Array[T](elems.length) val I = 0
for (elem <- elems) { result(i) = elem
I += 1
}
}
注意在 Java 數組的類型擦除模型下,以上方法不能按照指望的方式工做-實際上它將老是返回一個 Object 數組。
再次,在 Java 環境中有一個方法 System.arraycopy,有兩個對象爲參數,指定起始座標和長度,將元素從一個對象複製到另一個對象,這兩個對象的元素類型必須是兼容的。對於 Scala 的多態數組該方法不能正常工做,由於他們有不一樣的表現形式。做爲代替應當使用 Array 類的伴隨對象 Array.copy 方法。該伴隨對象也定義了不一樣的數組構造方法,還定義了提取方法 unapplySeq(§8.1.7),提供了數組上的模式匹配。
package scala
object Array {
/** 從‟src‟複製元素到‟dest‟ */
def copy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = ...
/** 將參數中全部數組合併爲一個 */
def concat[T](xs: Array[T]*): Array[T] = ...
/** 建立一個連續的整數數組 */
def range(start: Int, end: Int): Array[Int] = ...
/** 用給定元素建立一個數組 */
def apply[A <: AnyRef](xs: A*): Array[A] = ...
/** 與上面相似 */
def |
apply(xs: |
Boolean*) : |
Array[Boolean] |
= |
... |
def |
apply(xs: |
Byte*) : |
Array[Byte] |
= |
... |
def |
apply(xs: |
Short*) |
: Array[Short] |
= |
... |
def |
apply(xs: |
Char*) : |
Array[Char] |
= |
... |
def |
apply(xs: |
Int*) : |
Array[Int] |
= |
... |
def |
apply(xs: |
Long*) : |
Array[Long] |
= |
... |
def |
apply(xs: |
Float*) |
: Array[Float] |
= |
... |
def apply(xs: Double*) : Array[Double] = ...
def apply(xs: Unit*) : Array[Unit] = ...
/** 建立一個數組,包含一個元素的多個拷貝 */
def make[A](n: Int, elem: A): Array[A] = ...
/** 提供數組上的模式匹配 */
def unapplySeq[A](x: Array[A]): Option[Seq[A]] = Some(x)
}
示例 12.3.1 如下方法複製給定的數組參量並返回初始數組和複製的數組:
def duplicate[T](xs: Array[T]) = { val ys = new Array[T](xs.length) Array.copy(xs, 0, ys, 0, xs.length) (xs, ys)
}
package scala.xml
trait Node {
/** 該節點的標籤 */
def label: String
/** 屬性軸 */
def attribute: Map[String, String]
/** 子節點軸(該節點的全部子節點) */
def child: Seq[Node]
/** 下屬節點軸(該節點的全部下屬節點) */
def descendant: Seq[Node] = child.toList.flatMap{ x => x::x.descentant.asInstanceOf[List[Node]]
}
/** 下屬節點軸(該節點的全部下屬節點) */
def descendant_or_self: Seq[Node] = this::child.toList.flatMap{ x => x::x.descendant.asInstanceOf[List[Node]]
}
override def equals(x: Any): Boolean = x match {
case that:Node =>
that.label == this.label && that.attribute.sameElement(this.attribute) &&
that.child.sameElement(this.child)
case _ => false
}
/** XPath 形式的投影函數。返回該節點全部標籤爲‟that‟的子節點。保留它們在
* 文檔中的順序
*/
def \(that: Symbol): NodeSeq = {
new NodeSeq({ that.name match {
case 「_」 => child.toList
case _ =>
var res:List[Node] = Nil
for (x <- child.elements if x.label == that.name) { res = x::res
}
res.reverse
}
})
}
/** XPath 形式的投影函數。返回該節點的‟descendant_or_self‟軸中全部
* 標籤爲」that」的節點。保留它們在文檔中的順序。
def \\(that: Symbol): NodeSeq = {
new NodeSeq( that.name match {
case 「_」 => this.descendant_or_self
case _ => this.descendant_or_self.asInstanceOf[List[Node]] filter(x => x.label == that.name)
})
}
/** 該 XML 節點的 hashcode */
override def hashCode =
Utility.hashCode(label, attribute.toList.hashCode, child)
/** 該節點的字符串表現形式 */
override def toString = Utility.toXML(this)
}
Predef 對象爲 Scala 程序定義了標準函數和類型別名。該對象老是被隱式引入,它全部的成員不用聲明便可用。它在 JVM 環境中的定義與如下簽名一致:
package scala
object Predef {
// classOf ------------------------------------------------
/** 返回類類型的運行時表示 */
def classOf[T] = null
// 這個不是真實的返回值,該方法將由編譯器處理
// 標準類型別名
type byte = scala.Byte type short = scala.Short type char = scala.Char type int = scala.Int type long = scala.Long type float = scala.Float type double = scala.Double type boolean = scala.Boolean type unit = scala.Unit
type String = java.lang.String type Class[T] = java.lang.Class[T] type Runnable = java.lang.Runnable
type Throwable = java.lang.Throwable type Exception = java.lang.Exception type Error = java.lang.Error
type RuntimeException = java.lang.RuntimeException
type NullPointerException = java.lang.NullPointerException type ClassCastException = java.lang.ClassCastException type IndexOutOfBoundsException =
java.lang.IndexOutOfBoundsException
type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException
type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException
type UnsupportedOperationException = java.lang.UnsupportedOperationException
type IllegalArgumentException = java.lang.IllegalArgumentException
type NoSuchElementException = java.util.NoSuchElementException
type NumberFormatException = java.lang.NumberFormatException
// 雜項 -----------------------------------------------
type Function[-A, +B] = Function1[A, B]
type Map[A, B] = collection.immutable.Map[A, B]
type Set[A] = collection.immutable.Set[A]
val Map = collection.immutable.Map
val Set = collection.immutable.Set
// 錯誤與斷言 ------------------------------------------
def error(message: String): Nothing = throw new Error(message)
def exit: Nothing = exit(0)
def exit(status: Int): Nothing = { java.lang.System.exit(status) throw new Throwable()
}
def assert(assertion: Boolean) {
if(!assertion)
throw new java.lang.AssertionError(「assertion failed」)
}
def assert(assertion: Boolean, message: Any) {
if(!assertion)
throw new java.lang.ArrsertionError(「assertion failed: 「 + message)
}
def assume(assumption: Boolean, message: Any) {
if(!assumption)
throw new IllegalArgumentException(message.toString)
}
// 元組 ---------------------------------------------
type Pair[+A, +B] = Tuple2[A, B]
object Pair {
def apply[A, B](x: A, y: B) = Tuple2(x, y)
def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B] = Some(x)
}
type Triple[+A, +B, +C] = Tuple3[A, B, C]
object Trible {
def apply[A, B, C](x: A, y: B, z: C) = Tuple3(x, y, z)
def unapply[A, B, C](x: Tuple3[A, B, C]): Option[Tuple3[A, B, C]] = Some(x)
}
class ArrowAssoc[A](x: A){
def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
}
implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] =
new ArrowAssoc(x)
// 打印與讀取 -----------------------------------
def print(x: Any) = Console.print(x)
def println() = Console.println()
def println(x: Any) = Console.println(x)
def printf(text: String, xs: Any*) = Console.printf(text, xs:_*)
def format(test: String, xs: Any*) = Console.format(text, sx:_*)
def readLine(): String = Console.readLine()
def readLine(test: String, args: Any*) = Console.readLine(text, args)
def readBoolean() = Console.readBoolean()
def readByte() = Console.readByte() def readShort() = Console.readShort() def readChar() = Console.readChar() def readInt() = Console.readInt() def readLong() = Console.readLong() def readFloat() = Console.readFloat()
def readDouble() = Console.readDouble()
def readf(format: String) = Console.readf(format) def readf1(format: String) = Console.readf1(format) def readf2(format: String) = Console.readf2(format) def readf3(format: String) = Console.readf3(format)
// 「catch-all」隱式約定
implicit def identity[A](x: A): A = x
// Ordered 類中的視圖
implicit def int2ordered(x: Int): Ordered[Int] =
new Ordered[Int] with Proxy {
def self: Any = x
def compare[B >: Int <% OrderedB]](y: B): Int = y match {
case y1: Int =>
if (x < y1) -1 else if (x > y1) 1 else 0
case _ => -(y compare x)
}
}
// 如下方法的實現和上一個相似:
implicit def char2ordered(x: Char): Ordered[Char] = ... implicit def long2ordered(x: Long): Ordered[Long] = ... implicit def float2ordered(x: Float): Ordered[Float] = ... implicit def double2ordered(x: Double): Ordered[Double] = ... implicit def boolean2ordered(x: Boolean): Ordered[Boolean] = .
implicit def seq2ordered[A <% Ordered[A]](xs: Array[A]): Ordered[Seq[A]] = new Ordered[Seq[A]] with Proxy {
def compare[B >: Seq[A] <% Ordered[B]](that: B): Int = that match {
case that: Seq[A] =>
var res = 0
var these = this.elements var those = that.elements
while (res == 0 && these.hasNext)
res = if (!those.hasNext) 1 else these.next compare those.next
case _ => - (that.compare xs)
}
}
}
implicit def string2ordered(x: String): Ordered[String] =
new Ordered[String] with Proxy {
def self: Any = x
def compare [b >: String <% Ordered[b]](y: b): Int = y match {
case y1: String => x compare y1
case _ = - (y compare x)
}
}
implicit def tuple2ordered[a1 <% Ordered[a1],
a2 <% Ordered[a2]](x: Tuple2[a1, a2]): Ordered[Tuple2[a1, a2]] =
new Ordered[Tuple2[a1, a2]] with Proxy {
def self: Any = x
def compare[T >: Tuple2[a1, a2] <% Ordered[T]](y: T): Int = y match {
case y: Tuple2[a1, a2] =>
val res = x._1 compare y._1
if (res == 0) x._2 compare y._2
else res
case _ => - (y compare x)
}
}
// Tuple3 到 Tuple9 相似
// Seq 類中的視圖
implicit def string2seq(str: String): Seq[Char] =
new Seq[Char] {
def length = str.length
def elements = Iterator.fromString(str)
def apply(n: Int) = str.charAt
override def hashCode: Int = str.hashCode
override def equals(y: Any): Boolean = (str == y)
override protected def stringPrefix: String = 「String」
}
//從原生類型到 Java 裝箱類型的視圖
implicit def byte2Byte(x: Byte) = new java.lang.Byte(x)
implicit def short2Short(x: Short) = new java.lang.Short(x) implicit def char2Character(x: Char) = new java.lang.Character(x) implicit def int2Integer(x: Int) = new java.lang.Integer(x) implicit def long2Long(x: Long) = new java.lang.Long(x)
implicit def float2Float(x: Float) = new java.lang.Float(x) implicit def double2Double(x: Double) = new java.lang.Double(x) implicit def boolean2Boolean(x: Boolean) = new java.lang.Boolean(x)
// 數字轉換視圖
implicit def byte2short(x: Byte): Short = x.toShort implicit def byte2int(x: Byte): Int = x.toInt implicit def byte2long(x: Byte): Long = x.toLong implicit def byte2float(x: Byte): Float = x.toFloat implicit def byte2double(x: Byte): Double = x.toDouble
implicit def short2int(x: Short): Int = x.toInt implicit def short2long(x: Short): Long = x.toLong implicit def short2float(x: Short): Float = x.toFloat implicit def short2double(x: Short): Double = x.toDouble
implicit def char2int(x: Char): Int = x.toInt implicit def char2long(x: Char): Long = x.toLong implicit def char2float(x: Char): Float = x.toFloat implicit def char2double(x: Char): Double = x.toDouble
implicit def int2long(x: Int): Long = x.toLong implicit def int2float(x: Int): Float = x.toFloat implicit def int2double(x: Int): Double = x.toDouble
implicit def long2float(x: Long): Float = x.toFloat
implicit def long2double(x: Long): Double = x.toDouble
implicit def float2double(x: Float): Double = x.toDouble
}
我以爲 Scala 是一門很好的語言,成功揉合了 OO 和 FP。但願該譯本能給你們學習Scala 語言帶來幫助。
趙煒(wzhao1984@gmail.com)是個人同事。在翻譯過程當中提了很多建議,並全文翻譯了第八章。同時來自 EPFL 編程方法實驗室的 Antonio Cunei 也給了很多幫助和指導,並協助發佈了此文檔,在此一併表示感謝。
翻譯的起始時間大約是 2009 年 8 月份,翻譯過程總歷時約一年,總耗時約幾百個小時。原文的參考書目(Bibliography)語法總結(Scala Syntax Summary)和更新歷史(Change Log)
部分未作翻譯,也沒有包含在本文檔中,請參考原文獲取相關信息。
因爲水平所限,錯誤之處在所不免,若是不幸被您發現了,煩請發一封 email 至
gao_de@163.com,並註明頁碼章節,我將會在下個版本更新時一併更正。謝謝。
高德
2010 年 7 月 20 日於 上海