关于之前日志《F#编译器的一个Bug》,有一个问题,Set函数既然运行正常,改变的值去哪里了?以下例子做出了解答:
[<Struct>]
type TestStruct =
val mutable _x: int
new(x) = {_x = x}
member this.Set(x) =
this._x <- x
this
member this.X with get() = this._x;
let a = TestStruct(2)
let c() =
let b = a.Set 10
b.X
c();;
代码运行结果为10(我们想要的结果)。众所周知,类的子函数,其背后的实现是一个带对象输入的公共函数,调用a.Set(x),等效于执行伪代码:
Set(a, x) 。
由此可见,当我们绑定 let a = TestStruct(2),调用a.Set(x) 的时候,传递到Set函数中的this,其实是个复制值。
而当我们绑定 let mutable a = TestStruct(2),调用a.Set(x) 的时候,传递到Set函数中的this,却是a本身。
虽然,当我们在结构中使用mutable 的时候,可以使用返回this的方式,返回一个新的结构值。这也很符合函数编程的规范。但是如果可变数值使
用的是引用单元格,而不是mutable 的话。表现又略微不同:
mutable 方式,a值和Set返回值不同,一个表示原始值,另一个表示修改后的值;
引用单元格方式,a就是修改后的值。
[<Struct>]
type TestStruct =
val _x: int ref
new(x) = {_x = ref x}
member this.Set(x) =
this._x := x
member this.X with get() = !this._x;
let a = TestStruct(2)
let c() =
a.Set 10
a.X
c();;
以上这段代码,可以得到正确值10,原理很简单,int ref实际上是一个类,所以结构复制后,其对应的对象都是同一个。
所以用户仍然无法从习惯风格本身理解 TestStruct.Set(x)函数的行为。此外.Net库里面常用的一个StringBiulder类,它的行为特征,则完全符合引用单元格方式。所以结构中,mutable 的使用要尤其审慎。毕竟,一个结构,如果let绑定使用和let mutable绑定使用,两者之间行为不一致的话,利用这种特性写出的代码,未免太奇淫技巧一点,徒增读代码的难度。
F#: mutable 关键字不适宜用于结构中声明可变值