Scala 函数和闭包

一等函数

在 Scala 中,一等函数(first-class function)这个概念表示函数是“一等公民”,也就是说,函数可以赋值给变量,或者作为参数传递给其他函数,也可以作为函数的返回值返回给其他函数。

将函数赋值给变量

1val increase = (x: Int) => x + 1
2
3println(increase(0)) // 1

在上面的示例中,我们定义了一个函数并将其赋值给变量 increase。然后,我们调用这个函数并将结果打印出来。

将函数作为参数传递给其他函数

1val increase = (x: Int) => x + 1
2
3def applyFunction(f: Int => Int, value: Int): Int = f(value)
4
5val result = applyFunction(increase, 0)
6
7println(result)  // 1

在上面的示例中,我们定义了一个函数 applyFunction,它接收一个函数 f 和一个整数 value。然后,我们调用 applyFunction 函数并将 increase 函数作为参数传递给它,并将结果打印出来。

将函数作为返回值返回给其他函数

1def getIncreaseFunction(n: Int): Int => Int = (x: Int) => x + n
2
3val increase = getIncreaseFunction(1)
4
5println(increase(0)) // 1

在上面的示例中,我们定义了一个函数 getIncreaseFunction,它接收一个整数 n 并返回一个函数。然后,我们调用 getIncreaseFunction 函数并将返回的函数赋值给变量 increase。最后,我们调用 increase 函数并将结果打印出来。

将函数存储在数据结构中

由于函数是一等公民,因此我们可以将函数存储在 List 或 Map 这样的数据结构中,例如:

1val functions = List((x: Int) => x + 1, (x: Int) => x * 2)
2
3println(functions.map(f => f(2)))
4// Expected output:
5// List(3, 4)

在上面的示例中,我们在 List 中存储了两个函数,然后使用 map 方法将每个函数应用于 2,从而得到一个新的列表,其结果为 3 和 4。

函数字面量

Scala 中的函数字面量(function literal)是一种简洁的语法,用于定义匿名函数。

1val nums = List(1, 2, 3, 4, 5)
2
3val evenNums = nums.filter(x => x % 2 == 0)
4
5println(evenNums)
6// Expected output:
7// List(2, 4)

上面的示例中,我们在 filter 方法中传入了一个函数字面量用于过滤出偶数。

占位符语法

在函数字面量中可以使用占位符语法是函数字面量变得更加简洁,例如,如果想过滤处数组中的偶数,可以这样写:

1val nums = List(1, 2, 3, 4, 5)
2
3val evenNums = nums.filter(_ % 2 == 0)
4
5println(evenNums)
6// Expected output:
7// List(2, 4)

当编译器没有足够上下文推断参数类型时,你需要显示地给出类型信息,例如:

1def add = (_: Int) + (_: Int)
2
3println(add(1, 2)) // 3

在上面的示例中,(_: Int) + (_: Int) 定义了一个具有两个 Int 类型参数的函数。

注:

  • 在函数字面量中,只有当每个参数只被调用了一次时,才能够使用占位符语法。
  • 在函数字面量中如果有多个占位符,则它们分别代表了一个单独的参数,即第一个占位符代表了第一个参数,第二个占位符代表了第二个参数,以此类推。

部分应用函数

在 Scala 中,部分应用函数Partially Applied Function)允许你固定一个函数的部分参数,并生成一个新的函数,这个新函数只接受剩余的参数。这一特性可以使得函数调用更加灵活,也易于重用。

假设你已经定义了如下日志记录函数,该函数接收日期 date,日志级别 level 和日志消息 message 三个参数并打印一条格式化的日志信息:

1def log(date: String, level: String, message: String): Unit = {
2  println(s"[$date] - [$level] - $message")
3}

假如你想基于 log 函数创建一个日期固定为 2024-01-01 的日志记录函数,你可以使用下划线(_)来实现:

1val todayLog = log("2024-01-01", _, _)
2
3todayLog("INFO", "This is a log message.")
4todayLog("WARN", "This is a warning message.")
5todayLog("ERROR", "This is an error message.")
6// Expected output:
7// [2024-01-01] - [INFO] - This is a log message.
8// [2024-01-01] - [WARN] - This is a warning message.
9// [2024-01-01] - [ERROR] - This is an error message.

闭包

在 Scala 中,闭包Closure)是一种函数,它可以捕获并使用其词法范围之外的变量。简言之,如果一个函数引用了它的外部变量,那么这个函数+被引用的变量就构成了一个闭包。

函数闭包具有如下功能:

  • 捕获自由变量:闭包可以使用在函数体外定义的变量。
  • 影响外部状态:闭包可以捕获自由变量,因此也可以修改自由变量,从而影响外部状态。
  • 动态创建函数:通过闭包可以创建更能更为丰富的函数,这些函数可以根据上下文信息操作变量。

一个简单的闭包示例如下:

1var greeting = "Hello"
2val sayHelloTo = (name: String) => s"$greeting, $name!"
3
4println(sayHelloTo("World")) // Hello, World!
5
6greeting = "Hola"
7println(sayHelloTo("World")) // Hola, World!

在上面的示例中,我们定义了一个变量 greeting,并创建了一个闭包 sayHelloTo,该闭包引用了外部变量 greeting。当我们修改变量 greeting 的值时,闭包 sayHelloTo 中也会使用修改后的值。

上一页
下一页