要回答这个问题,我们需要能够创建一些要捕获的状态。一个方法是用 let mutable,但是,这样,并不能运行,因为这种可变值只能用于局部,不能被闭包捕获。
第二个方法是使用引用(ref)类型创建可变值,即引用单元(reference cell)的缩写,它是能够包含可变值的小对象(实际上,声明为 F# 的记录类型)。要理解引用类型的原理,我们在 C# 中定义同样的类型,可以看到,相当简单:
class Ref<T> {
public Ref(T value) { Value = value;}
public T Value { get; set; }
}
这里最重要的是 Value 属性是可变的,所以,当我们创建了一个不可变的 Ref<int>类型的变量时,仍然可以改变它代表的值。清单 8.8 是在 F# 中使用引用单元的例子,显示了相当于 C# 使用Ref<T> 类型的代码;在 F# 中,不直接访问类型,因为有一个函数,也叫 ref,创建引用单元,以及设置和读取值的两个运算符。
清单 8.8 在 F# 和 C# 中使用引用单元
F# Interactive |
C# |
let st = ref 10 st := 11 printfn "%d" (!st) |
var st = new Ref<int>(10); st.Value = 11; Console.WriteLine(st.Value); |
第一行,我们创建了一个包含整数的引用单元,如同刚刚在 C# 中声明的 Ref<T> 类型一样,F# 的 ref 类型是泛型的,所以,我们可以用它来保存任何类型的值;接下来的两行演示了使用引用单元的运算符:赋值(:=)和取消引用(!),F# 的运算符对应于设置和读取属性值,但语法更便利。
[
在 F# 4.0 中,对可变值的使用进行了简化。
在 F# 中,虽然值默认是不可变的,但是,可变值也是是允许的。过去,关键字 mutable用于说明可变值使用栈,这种值使用 <- 运算符进行修改;如果可变值是由闭包捕获的,那么值就使用堆,需要使用ref 语法,愈发值是通过 := 运算符。
这些语法上的差异既使代码不优雅,也让开发人员困惑,很难弄清楚到底应该使用哪一种方法。
现在,使用 F# 4.0,开发人员只要用一个关键字 mutable,就能搞定所有可变值,余下的工作由编译器完成:如果可能,就在栈上创建变,否则,隐式转换成引用单元。
对于非常了解栈、堆语有钱人高级用户,能够启用警告 3180(在 fsc.exe 或 fsi.exe 中,使用 --warnon:3180),当 mutable 声明隐式转换成 ref 时,会发出通知。
]