跳至内容

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。

华夏公益教科书