Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

闭包

X 语言支持函数式编程特性,包括闭包——可以捕获其环境中值的匿名函数。在本章中,我们将了解闭包是什么、它们如何工作以及何时使用它们。

什么是闭包?

闭包是可以存储在变量中或作为参数传递给其他函数的匿名函数。与函数不同,闭包可以捕获它们定义的作用域中的值。

让我们从一个简单的闭包示例开始:

let add_one = function(x) { x + 1 }

println(add_one(5))  // 6

这里,add_one 是一个接受一个参数 x 并返回 x + 1 的闭包。闭包的语法类似于函数,但有一些区别:

  • function 关键字(可选的,在某些上下文中)
  • 参数周围的括号
  • 箭头 =>(可选的,取决于语法)
  • 没有显式类型注解(通常可以推断)

闭包类型推断

与函数不同,闭包通常不需要你注解参数或返回值的类型。类型是从闭包的使用方式推断出来的。

// 闭包不强制类型注解
let add = function(x, y) { x + y }

// 第一次使用确定类型
let result = add(5, 10)  // add 现在是 integer -> integer -> integer
println(result)  // 15

但是,如果你想显式注解类型,你可以这样做:

let add: function(integer, integer) -> integer = function(x, y) { x + y }

捕获环境

闭包的一个强大特性是它们可以捕获其环境——它们定义的作用域中的变量。

function main() {
  let x = 4
  let equal_to_x = function(z) { z == x }
  let y = 4
  println(equal_to_x(y))  // true
}

这里,equal_to_x 闭包从其环境中捕获了变量 x。它将 x 的值与它接受的参数 z 进行比较。

捕获方式:借用与移动

闭包可以通过三种方式捕获它们的环境,对应于函数获取参数的三种方式:不可变借用、可变借用和获取所有权。闭包会自动确定使用哪种方式,具体取决于它如何使用捕获的值。

不可变借用

如果闭包只读取值,它将通过不可变借用捕获它:

let list = [1, 2, 3]
let only_borrows = function() { println("我借用了列表: {:?}", list) }
only_borrows()  // 我借用了列表: [1, 2, 3]

可变借用

如果闭包修改值,它将通过可变借用捕获它:

let mutable list = [1, 2, 3]
let mutable borrows_mutably = function() { list = list + [4] }
mut_borrows_mutably()
println(list)  // [1, 2, 3, 4]

移动

如果闭包获取值的所有权(例如,如果它将值返回或将其移动到别处),它将通过移动捕获值:

let list = [1, 2, 3]
let takes_ownership = function() {
  let moved_list = list
  println("我拥有了列表: {:?}", moved_list)
}
takes_ownership()
// println(list)  // 错误!list 已经被移动

我们也可以使用 move 关键字强制闭包获取它使用的环境值的所有权,即使它在技术上不需要这样做:

let list = [1, 2, 3]
let owns_list = move function() {
  println("我拥有了列表: {:?}", list)
}
owns_list()
// println(list)  // 错误!list 已经被移动

当我们将闭包传递给新线程时,move 关键字最常用,因为我们想将数据的所有权从一个线程转移到另一个线程。我们将在关于并发的章节中看到这方面的例子。

将闭包作为参数

闭包作为函数参数非常有用。让我们创建一个接受闭包作为参数的函数:

function apply_twice(f: function(integer) -> integer, x: integer) -> integer {
  f(f(x))
}

let add_two = function(x) { x + 2 }
let result = apply_twice(add_two, 5)
println(result)  // 5 + 2 + 2 = 9

在这里,apply_twice 接受一个函数 f 和一个整数 x,并将 f 应用于 x 两次。

返回闭包

我们也可以从函数返回闭包。但是,闭包类型是匿名的,所以我们需要使用 impl 语法或 trait 对象:

function create_adder(x: integer) -> impl function(integer) -> integer {
  function(y) { x + y }
}

let add_five = create_adder(5)
println(add_five(3))  // 8

create_adder 接受一个整数 x 并返回一个将 x 添加到其参数的闭包。返回的闭包捕获 x

闭包作为迭代器适配器

闭包在迭代器中特别有用,我们将在下一章讨论。这是一个预览:

let numbers = [1, 2, 3, 4, 5]
let doubled: List<integer> = numbers
  .iter()
  .map(function(n) { n * 2 })
  .collect()
println(doubled)  // [2, 4, 6, 8, 10]

闭包与函数指针

你也可以使用普通函数代替闭包,当你想要的逻辑不需要捕获环境中的任何内容时:

function add_one(x: integer) -> integer {
  x + 1
}

let result = apply_twice(add_one, 5)
println(result)  // 7

闭包的性能

你可能想知道闭包是否有性能成本。好消息是,X 语言中的闭包被编译为高效的代码——通常与手写函数一样高效!每个闭包都有自己独特的类型,即使两个闭包具有相同的签名,因此编译器可以专门化并优化每个闭包的使用。

总结

X 语言中的闭包:

  • 是可以捕获其环境的匿名函数
  • 可以存储在变量中并作为参数传递
  • 可以通过不可变借用、可变借用或移动捕获值
  • 可以使用 move 关键字强制获取所有权
  • 可以作为函数参数和返回值
  • 对于迭代器和高阶函数特别有用
  • 编译为高效代码,没有运行时开销

闭包是 X 语言函数式编程工具包的重要组成部分。在下一章中,我们将讨论迭代器,它们经常与闭包一起使用!