8.2.3 在 F# 使用闭包捕获状态

时间:2022-03-05 20:18:15

8.2.3 在 F# 使用闭包捕获状态

 

在这一节,我们要讨论闭包(closures),在函数编程中,这是一个非常重要的概念。闭包很常见,大部分时间它们都不使用可变状态;不过,出于实用的考虑,使用可变状态有时也是需要的,闭包能够提供了一种极好的方式,限制可变状态的作用域。

首先,我们看一段简单的 F# 代码片段,在第五章已经看过:

 

> let createAdder num = 

     (fun m -> num +m);; 

val createAdder : int -> int –> int

 

在我们前面的讨论中,我们没有看出,像这样写的函数,与调用加法,取两个参数并返回和的函数之间有什么区别。这是因为,我们能够只用一个参数值调用加法函数:因为有了散应用,其结果是一个函数,将特定数加到任何给定的参数值上。

如果仔细分析一下前面示例的返回结果,它不仅是函数的代码!代码是一组指令,加两个数字,但是,如果我们用两个不同的参数,调用 createAdder 两次,返回的函数显然不同,因为,它们加的数不同。关键的思想是,函数不仅是代码,也是闭包,包含了由函数使用、但不在函数体内声明的值。由闭包控制的值称为捕获(captured)。在前面的例子中,唯一的捕获是 num 参数。

闭包听起来复杂,实际上简单。F# 编译器使用抽象类 FastFunc<int, int> 表示参数为整数,返回结果为整数的函数值。清单 8.7 显示了createAdder函数码转换为 C# 生成的代码。

 

清单 8.7 F# 编译器生成的闭包类 (C#)

class createAdder : FastFunc<int,int> { 

  public int num;     <-- 保存捕获的值

 

  internal createAdder(int num){   [1]

    this.num = num; 

  }

  public override int Invoke(int m) {   <-- 运行构造的函数

    return this.num +m; 

  } 

}

 

static FastFunc<int, int>createAdder(int num) {   [2]

  return new createAdder(num); 

}

 

编译器生成一个静态方法createAdder[2],相当于 F# 的函数。方法构造了一个函数值,由函数代码组成,保存了闭包捕获的值。生成的闭包类的参数取为捕获的值,在我们的示例中,只有一个参数 num[1]。在用虚拟方法 Invoke 运行这个函数值时,代码可以访问保存在闭包中的值。

当然,自从我们开始讨论lambda 函数,就一直在使用闭包创建函数,只是没有明确地讲出来,因为,通常不需要考虑这些,它们确实就在运行。然而,如果闭包捕获到可变值,又会怎样呢?