4.3. 匿名函数及闭包

上一节介绍了函数值的基本用法,既然函数可被视为值,那么,在凹语言函数内部,是否可以像声明基本类型字面量那样,声明函数字面量?答案是肯定的,例如:

// 版权 @2023 凹语言 作者。保留所有权利。

func useFunc(i: i32, f: func(i32) => i32) {
    if f == nil {
        println("f == nil")
        return
    }
    println(f(i))
}

func main {
    f := func(i: i32) => i32 { return i * i } // 声明匿名函数并赋值给 f
    useFunc(3, f) // 9
}

其中快捷声明的函数值 f,它的初始值是字面量 func(i: i32) => i32 { return i * i },既一个没有名字的函数。在凹语言中,这种没有名字的函数字面量被称为匿名函数。在访问者模式、自定义快速排序等应用场景中,经常需要传入一些函数值参数,而这些函数可能仅在当前上下文环境出现一次,为此额外定义模块级的全局函数有诸多不便,这时即可使用匿名函数。

在函数A内部声明的匿名函数B,可以访问A内部的局部变量,例如:

// 版权 @2023 凹语言 作者。保留所有权利。

func useFunc(i: i32, f: func(i32) => i32) {
    if f == nil {
        println("f == nil")
        return
    }
    println(f(i))
}

func main {
    n: i32 = 0
    f := func(i: i32) => i32 {
        n = i * i
        return n
    }
    useFunc(3, f)
    println(n) // 9
}

可见函数值 f 可以读写外层的局部变量 n。再来看一个更加复杂的例子:

// 版权 @2023 凹语言 作者。保留所有权利。
func genClosure(i: i32) => func() => i32 {
    n := i
    return func() => i32 {
        n = n + 1
        return n
    }
}

func main {
    c := genClosure(0)
    d := genClosure(99)

    println(c()) // 1
    println(d()) // 100
    println(c()) // 2
    println(d()) // 101
}

每次调用 genFunc 都将生成一个函数值,这个函数值捕获了局部变量 n,函数值每次执行会对捕获的 n 执行加1,多次执行 genFunc 所获得的函数值,它们捕获的 n 是不同的,每执行一次,捕获一个新的实例。

在函数内声明的匿名函数值,携带了本次运行时捕获的局部变量的状态。显然,这种函数值实质上就是闭包。