跳至內容

Scala/入門

來自華夏公益教科書

第一步

[編輯 | 編輯原始碼]

在本篇簡短的 Scala 入門教程中,將介紹和解釋一些非常基礎的程式。某些部分將在後續文章中進行更詳細的解釋。

下面展示了簡單的 "Hello World!" 程式。

println("Hello World!") //Prints "Hello World!".

"println" 是一個函式,它接收一些輸入,列印它,並在末尾追加一個換行符。輸入作為引數給出,用圓括號括起來,在本例中是字串 "Hello World!"。圓括號後是一個單行註釋,以兩個斜槓開頭。斜槓後的所有內容都被視為註釋,編譯器會忽略它們。

在前面的示例中,輸入立即被消耗。如果我們想要儲存它呢?一種方法是宣告一個值並將輸入賦值給它

val helloWorldString = "Hello World!"
println(helloWorldString) //Prints "Hello World!".

在第一行,我們使用 "val" 後跟一些名稱,這裡為 "helloWorldString",來宣告一個值。我們使用 "=" 後跟一些表示式,這裡為字串 "HelloWorld",來賦值。值就像常量變數;它們必須始終被賦值,並且不能被重新賦值其他表示式。在下一行,"println" 使用 "helloWorldString" 的值,並列印 "Hello World!"。

為了重新賦值一個變數,必須使用關鍵字 "var" 而不是 "val"

var number = 24
println(number) //Prints "24".
number = 42
println(number) //Prints "42".

在第一行,"var" 後跟一些名稱,這裡為 "number",宣告一個變數。 "=" 後跟一些表示式,這裡為數字 24,將一些值賦值給變數。在第二行,我們列印這個數字,它為 24。在第三行,變數 "number" 已經宣告過了,所以如果我們要給它賦值表示式,就不需要再次使用 "var" 來宣告它。這一次,我們賦值數字 "42",它會替換先前的值,當我們在第四行列印它時,我們會列印 "number" 的當前值,即 42。將值優先於變數是一種良好的風格,因為知道我們使用的識別符號始終引用同一個值,並且永遠不會改變,這使得我們更容易理解程式碼。

僅僅賦值和列印簡單表示式並不令人興奮。讓我們嘗試一些算術運算

val number2 = 24*3 + 100 - 130
println(number2) //Prints "42".

在第一行,我們聲明瞭一個名為 "number2" 的值,並給它賦值一個更復雜的表示式。表示式被計算,表示式的結果被儲存在 "number2" 中。在第二行,我們列印結果值,它為 42。請注意,運算子 "* "," + " 和 " - " 遵循數字的正常數學優先順序;即 "*" 的優先順序高於 "+" 和 "- "。

可以將數字和字串組合在一起。這是使用 "+" 方法完成的

val number3 = 124
println("The value of 'number3' is: " + number3 + ".") //Prints "The value of 'number3' is: 124.".

If-then-else 表示式

[編輯 | 編輯原始碼]

到目前為止,我們的程式只有一種控制流,即執行程式時只有一種執行方式。這使得我們難以根據程式的狀態改變行為。改變這種狀態的一種方法是使用 "if-then-else" 表示式

if (4 > 3) println("4 > 3 is true") else println("4 > 3 is false") //Prints "4 > 3 is true".

在第一行,"if" 用於開始 "if-then-else" 表示式。它後面跟著一對圓括號,裡面包含一個產生真值測試表達式的結果,這裡為 4 > 3。這是 "if-then-else" 的第一個分支。圓括號後是另一個表示式,這裡為 "println("4 > 3 is true")”。它後面可以跟著 "else" 和另一個表示式,這裡為 "println("4 > 3 is false")”。這是 "if-then-else" 的第二個分支。如果圓括號中的測試表達式為真,則執行第一個分支,在本例中為真,並且列印 "4 > 3 is true"(幸運的是,這確實是正確的)。如果表示式 4 > 3 不是真,則執行第二個分支。請注意,第二個分支是可選的;如果沒有第二個分支,並且測試表達式產生 false,則不會發生任何事情

if (1 < 0) println("1 < 0 is true")
//Nothing happens.

由於我們只有一個分支,並且測試表達式為假,因此不會發生任何事情。

在 Scala 中,if-then-else 表示式有一個結果值。這通常可以使我們的程式更短

println(if (4 > 3) "4 > 3 is true" else "4 > 3 is false") //Prints "4 > 3 is true".

if-then-else 表示式被求值,生成一個字串 ("4 > 3 is true"),它被 "println" 函式列印,從而產生與以前相同的列印結果。請注意,我們只使用了一次 "println",而不是兩次。

程式碼塊

[編輯 | 編輯原始碼]

程式碼塊是一個包含多個連續表示式的表示式。下面展示了一個使用程式碼塊的示例

val area = {
  val r = 3.5
  math.Pi * r * r
}
println(math.round(area)) // Prints "38".

在第一行,我們聲明瞭一個名為 "area" 的 val,並給它賦值一個程式碼塊。程式碼塊以 "{" 開頭,後面跟著多個表示式,這裡為 "val r = 3.5" 和 "math.Pi*r*r",最後以 "}" 結束。第一個表示式將 3.5 賦值給名為 "r" 的值,第二個表示式包含一個表示式,它計算一個圓的面積,其中 "r" 表示圓的半徑。程式碼塊中的最後一個表示式給出程式碼塊的結果,在本例中它生成一個接近 38 的數字。這個數字被賦值給名為 "area" 的值。在最後一行,我們對 "area" 的值進行四捨五入並列印它,打印出 38。

請注意,在作用域內宣告的值和變數在作用域外不可見。這意味著我們無法在作用域外訪問 "r"。在某些情況下,將臨時值和變數隱藏在作用域內可以幫助我們避免程式中出現太多名稱。

While 迴圈

[編輯 | 編輯原始碼]

If-then-else 表示式允許我們讓程式根據其狀態執行不同的程式碼。另一個有用的控制流修改是多次重複相同的程式碼。While 迴圈可以用來完成這項工作

var i = 0
while (i < 10) {print(i + " "); i += 1} //Prints "0 1 2 3 4 5 6 7 8 9 ".

第一行聲明瞭一個名為 "i" 的變數,並給它賦值 0。第二行宣告一個 while 迴圈,以 "while" 開頭,後面跟著一個圓括號中的測試表達式,這裡為 "i < 10",然後是一個主表示式,在本例中是一個作用域。作用域包含兩個語句,分別是 "print(i + " ")" 和 "i += 1"。 ";" 用於分隔這兩個語句,這樣我們就可以在一行上編寫這兩個語句。

while 迴圈的執行過程如下:首先,測試表達式被求值。如果它不是真,則停止執行 while 迴圈,控制流在 while 迴圈之後繼續。如果它是真,則執行 while 迴圈的主表示式,這裡是一個作用域。作用域執行完畢後,控制流返回到測試表達式,並重復此過程。

在本例中,我們評估 "i" 是否小於 10。一開始,"i" 為 0,所以這是真的。因此,我們執行主表示式,它列印 "i" 的值並新增一個空格,然後增加 "i" 的值。然後我們再次回到測試表達式,並評估 "i" 是否小於 10。由於 "i" 現在為 1,所以表示式仍然為真,所以我們再次進入主表示式,列印 "i" 的值並新增一個空格,然後增加 "i"。此過程持續進行,直到 "i" 的值變為 10,之後我們停止執行 while 迴圈,並在其之後繼續執行程式。

For 表示式

[編輯 | 編輯原始碼]

Scala 透過使用 "for 表示式"(也稱為 for 推導和序列推導)提供了對集合操作的語法支援。有關正式背景,請參閱列表推導的相應解釋:http://en.wikipedia.org/wiki/List_comprehension#Overview.

為了使用 for 表示式,我們必須使用一些集合。一種選擇是範圍

println(1 to 10) //Prints "Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)".

在第一行,方法 "to" 被呼叫,引數為 1,引數為 10,它生成從 1 包含到 10 包含的按順序排列的數字範圍。現在,讓我們使用 for 表示式列印每個數字的平方

for (i <- 1 to 10) print(i*i + " ") //Prints "1 4 9 16 25 36 49 64 81 100 ".

我們首先使用 "for" 宣告 for 表示式。後面跟著一對括號,包含 "i <- 1 to 10"。 "<-" 是內建語法,表示左側的名稱引用右側集合中的元素。因此,在本例中,名稱 "i" 引用集合 "1 to 10" 中的元素。括號後面跟著一個輸出表達式。該表示式會針對集合中的每個元素執行一次。在每次執行時,名稱 "i" 都會引用一個新的元素。因此,"i*i + " "" 會被重複列印,每次都使用 "i" 的新值。

如果我們不想列印所有元素,該怎麼辦?在這種情況下,我們會使用過濾器。

for (i <- 1 to 10 if i % 2 == 0) print(i*i + " ") //Prints "4 16 36 64 100 ".

這次我們在 "i <- 1 to 10" 後添加了 "if i % 2 == 0"。 "if" 表示過濾器的開始,而 "i % 2 == 0" 是一個測試表達式,這裡用於測試 "i" 是否為偶數。只有使測試表達式為真的元素才會在輸出表達式中執行。因此,這次只打印偶數。

在上面的例子中,數字總是被打印出來。但是,for 表示式可以從輸出表達式中生成一個新的集合。

val evenNumbersSquared = for (i <- 1 to 10 if i % 2 == 0) yield i*i
println(evenNumbersSquared.toList) //Prints "List(4, 16, 36, 64, 100)".

在第一行,有兩件事發生了變化。首先,我們將 for 表示式的結果賦值給值 "evenNumbersSquared"。其次,我們現在在輸出表達式 "i*i" 之前使用了 "yield" 關鍵字。這意味著,而不是對輸出表達式不做任何操作,輸出表達式被用來透過指示下一個元素應該是什麼來構建一個新的集合。在第二行,我們將結果集合 "evenNumbersSquared" 轉換為列表,並打印出來。

可以在 for 表示式中使用多個集合。

for (i <- 1 to 4; a <- 1 to 4 if i < a) print(i + ":" + a + "  ") //Prints "1:2  1:3  1:4  2:3  2:4  3:4  ".

我們再次使用 "for" 開始 for 表示式。在括號中,我們現在有兩個繫結到集合元素的名稱: "i <- 1 to 4" 和 "a <- 1 to 4"。 ";" 用於分隔這兩個部分。與之前一樣,後面跟著一個測試表達式 "i < a",它這次測試 "i" 是否小於 "a"。最後,我們列印透過過濾器的 "i" 和 "a" 對,最終打印出從 1 到 4 的所有數字對,其中第一個數字小於第二個數字。

需要注意的是,for 表示式是呼叫特定高階函式的語法糖。那麼為什麼要使用 for 表示式呢?for 表示式有時比使用它們相應的高階函式更清晰、更簡單。

還需要注意的是,for 表示式並不侷限於集合。任何支援正確高階函式的類都可以與 for 表示式一起使用。除了集合之外,標準庫中的示例包括 Option、Either 和解析器組合器 Parser。

華夏公益教科書