ZetCode

Scala 函数

最后修改于 2023 年 1 月 10 日

在本文中,我们介绍 Scala 中的函数。

一个函数

一个函数是将零个或多个输入值映射到零个或多个输出值。

使用函数,我们可以减少代码重复并提高其清晰度。更复杂的任务可以使用函数分解为更简单的单元。

函数可以赋值给变量,作为参数传递给函数,或从其他函数返回。

在 Scala 中,有两种类型的函数:val 函数和 def 函数。这两种类型的函数之间存在一些技术差异。在需要时,def 函数会转换为带有下划线运算符的 val 函数。这种转换通常是自动进行的,因此区别有些模糊。

Scala 主函数

主函数是 Scala 应用程序的入口点。它使用 `@main` 注释进行装饰。

main.scala
@main def main() =

    println("main function is an entry point")

主函数的名称和参数由 `=` 运算符终止。运算符后面是代码块。在 Scala 3 中,空格是语法的一部分。它用于分隔函数体。

println("main function is an entry point")

`println` 是一个内置函数,它将文本打印到终端并添加换行符。

$ scala main.scala
main function is an entry point

主函数可以接受参数。

main.scala
@main def main(args: String*) =

    args.foreach(println)

在程序中,主函数可以接受任意数量的字符串参数。

$ scala main.scala an old falcon
an
old
falcon

返回值

传统上,`return` 关键字用于从函数返回一个值。在 Scala 中,`return` 关键字是可选的。

main.scala
def square(x: Int): Int =

    // return x * x
    x * x

@main def main() =

    println(square(5))

我们定义了一个 `square` 函数。

def square(x: Int): Int =

`def` 关键字后面是函数名。在方括号中,我们提供函数参数。括号后面是冒号和返回值的类型。在 `=` 字符之后,我们提供函数体。

// return x * x
x * x

在 Scala 中,`return` 关键字是可选的。最后一个表达式的值会自动返回给调用者。

println(square(5))

我们使用数字 5 作为参数调用 `square` 函数。计算出的值将打印到控制台。

$ scala main.scala
25

可变数量的参数

使用星号定义可变数量的参数。

main.scala
def mysum(vals: Int*): Int =

    var total = 0

    for n <- vals do
        total += n

    total

@main def main() =

    val s1 = mysum(1, 2, 3)
    val s2 = mysum(1, 2, 3, 4)
    val s3 = mysum(1, 2, 3, 4, 5)

    println(s1)
    println(s2)
    println(s3)

该程序包含一个 sum 函数的定义。该函数可以接受任意数量的参数。这些参数在函数体内作为值序列可用。

def mysum(vals: Int*): Int =

    var total = 0

    for n <- vals do
        total += n

    total

这是自定义 `mysum` 函数的定义。`vals` 是一个整数值序列。通过 for 循环,我们计算最终的总和。

val s1 = mysum(1, 2, 3)
val s2 = mysum(1, 2, 3, 4)
val s3 = mysum(1, 2, 3, 4, 5)

我们将三个、四个和五个值传递给函数。

$ scala main.scala
6
10
15

Scala 默认函数参数

Scala 函数参数可以具有默认值;如果未为参数提供值,则使用这些默认值。

main.scala
def power(x: Int, n: Int = 2): Int =

    if n == 2 then
        x * x

    var res = 1
    var i = 0

    while i < n do
        res *= x
        i += 1

    res


@main def main() =

    val r1 = power(3)
    println(r1)

    val r2 = power(3, 3)
    println(r2)

我们有一个 `power` 函数。该函数有一个带有隐式值的参数。我们可以用一个或两个参数调用该函数。

def power(x: Int, n: Int = 2): Int =

`power` 函数的第二个参数是隐式的。如果未提供,其值为 2。

$ scala main.scala
9
27

Scala 匿名函数

匿名函数没有名称。在许多情况下,定义一个命名函数是多余的。

main.scala
@main def main() =

    val nums = List(1, 2, -3, -4, 5)

    val pos = nums.filter(e => e > 0).map(e => e * 2)
    println(pos)

    val neg = nums.filter(_ < 0)
    println(neg)

    nums.foreach(e => print(s"$e "))
    println

我们有一个整数列表。我们在列表上调用了一些 `filter`、`map` 和 `foreach` 函数。

val pos = nums.filter(e => e > 0).map(e => e * 2)

`filter` 和 `map` 是作用于列表元素的函数。它们将一个谓词函数(返回布尔值的函数)作为参数。在我们的例子中,谓词是匿名函数。

val neg = nums.filter(_ < 0)

这是简化的语法。

$ scala main.scala
List(2, 4, 10)
List(-3, -4)
1 2 -3 -4 5

Scala val 函数

使用 `val` 关键字,我们定义函数类型。

main.scala
val square = (x: Int) => x * x
val triple: (x: Int) => Int = (x) => x * x * x

@main def main() =

    val nums = List(1, 2, 3, 4, 5)

    val res = square(3)
    println(res)

    val res2 = triple(5)
    println(res2)

    val res3 = nums.map(square)
    println(res3)

    val square2 = square
    println(square2(5))

该程序定义了两个 val 函数。

val square = (x: Int) => x * x

函数类型绑定到 `square` 标识符。函数的参数与函数体通过胖箭头 `=>` 运算符分隔。`square` 标识符的类型声明被省略,因为 Scala 能够推断它。

val triple: (x: Int) => Int = (x) => x * x * x

这里我们省略了函数参数的类型,但提供了 `tripe` 标识符的类型。(不能同时省略两个声明。)

val res = square(3)
println(res

val res2 = triple(5)
println(res2)

val res3 = nums.map(square)
println(res3)

val 函数的调用方式与 def 函数完全相同。

$ scala main.scala
9
125
List(1, 4, 9, 16, 25)
25

val 函数和 def 函数的一个区别是 def 函数总是被求值。

main.scala
val getNano = System.nanoTime
def getNano2 = System.nanoTime

@main def main() = 

    println(getNano)
    Thread.sleep(300)
    println(getNano)

    println("-----------------")

    println(getNano2)
    Thread.sleep(300)
    println(getNano2)

`getNano` 和 `getNano2` 函数返回当前的纳秒时间。

val getNano = System.nanoTime

`getNano` 函数只求值一次,后续调用返回相同的值。

def getNano2 = System.nanoTime  

`getNano2` 函数始终被求值。

$ scala main.scala
32724329770177
32724329770177
-----------------
32724630310958
32724930546360

Scala 嵌套函数

嵌套函数,也称为内部函数,是在另一个函数内部定义的函数。

main.scala
def minmax(x: Int, y: Int) = 

    val min = (x: Int, y: Int) => if x < y then x else y
    val max = (x: Int, y: Int) => if x > y then x else y

    var mn = min(x, y)
    var mx = max(x, y)
    
    (mn, mx)

@main def main() = 

    val res = minmax(100, 13)
    println(s"min: ${res._1} max: ${res._2}")

    val res2 = minmax(0, -13)
    println(s"min: ${res2._1} max: ${res2._2}")

在该程序中,我们有一个 `minmax` 函数,其中包含两个嵌套函数:`min` 和 `max`。

$ scala main.scala 
min: 13 max: 100
min: -13 max: 0

Scala 高阶函数

高阶函数通过将其他函数作为参数或返回其他函数来操作它们。

main.scala
def process(data: List[Int], f: (e: Int) => Int): List[Int] =

    data.map(f)


@main def main() =

    val nums = List(1, 2, 3, 4, 5, 6)

    val res1 = process(nums, e => e * e)
    println(res1)

    val res2 = process(nums, e => e + 1)
    println(res2)

`process` 是一个高阶函数。

def process(data: List[Int], f: (e: Int) => Int): List[Int] =

    data.map(f)

`process` 高阶函数将函数应用于列表。

val res1 = process(nums, e => e * e)
...
val res2 = process(nums, e => e + 1)

我们将两个不同的匿名函数传递给 `process` 函数。

$ scala main.scala
List(1, 4, 9, 16, 25, 36)
List(2, 3, 4, 5, 6, 7)

Scala 闭包

闭包是一个匿名嵌套函数,它保留对定义在闭包体外部的变量的绑定。

闭包可以持有自己独特的状态。当我们创建函数的新实例时,状态会得到隔离。

main.scala
def intSeq(): () => Int =

    var i = 0

    return () => { i += 1; i }

@main def main() =

    val nextInt = intSeq()

    println(nextInt())
    println(nextInt())
    println(nextInt())
    println(nextInt())

    println("-------------------")

    val nextInt2 = intSeq()
    println(nextInt2())
    println(nextInt2())

我们有一个 intSeq 函数,它生成一个整数序列。它返回一个递增 `i` 变量的闭包。

def intSeq(): () => Int =

    var i = 0

    return () => { i += 1; i }

在函数中定义的变量具有局部函数作用域。但是,在这种情况下,即使在 `intSeq` 函数返回后,闭包仍绑定到 `i` 变量。

val nextInt = intSeq()

调用了 `intSeq` 函数。它返回一个递增计数器的函数。返回的函数通过关闭变量 `i` 来形成闭包。闭包绑定到 `nextInt` 值。

println(nextInt())
println(nextInt())
println(nextInt())
println(nextInt())

我们调用闭包四次。

val nextInt2 = intSeq()
println(nextInt2())
println(nextInt2())

下一次调用 `intSeq` 函数会返回一个新的闭包。这个新的闭包有自己独立的状态。

$ scala main.scala 
1
2
3
4
-------------------
1
2

Scala 扩展函数

扩展函数为现有类型添加功能。扩展函数使用 `extension` 关键字创建。

main.scala
extension (value: Int)

    def isOdd = value % 2 == 0
    def isEven = value % 2 != 0

    def times(f: Any => Unit, v: Any): Unit =

        var i = 0
        while i < value do
            f(v)
            i += 1


@main def main() =

    val n = 4

    println(n.isOdd)
    println(n.isEven)

    n.times(println, "falcon")

在该程序中,我们将三个扩展函数添加到 `Int` 类型。

extension (value: Int)

    def isOdd = value % 2 == 0
    def isEven = value % 2 != 0

    def times(f: Any => Unit, v: Any): Unit =

        var i = 0
        while i < value do
            f(v)
            i += 1

`isOdd` 和 `isEven` 函数检查整数是偶数还是奇数。`times` 函数执行给定的函数 n 次。

$ scala main.scala
true
false
falcon
falcon
falcon
falcon

Scala 成员函数

成员函数是在 Scala 类中定义的函数。

main.scala
class Cat:

    def talk() =
        println("meow")


@main def main() =

    val missy = Cat()
    missy.talk()

`talk` 函数定义在 `Cat` 类中。

val missy = Cat()
missy.talk()

我们创建 `Cat` 对象并通过点运算符调用成员函数。

$ scala main.scala
meow

在本文中,我们学习了 Scala 函数。