F# 记录:ref 与可变字段
F# record: ref vs mutable field
在重构我的 F# 代码时,我发现了一个字段类型为 bool ref
:
的记录
type MyType =
{
Enabled : bool ref
// other, irrelevant fields here
}
我决定尝试将其改为 mutable
字段
// Refactored version
type MyType =
{
mutable Enabled : bool
// other fields unchanged
}
此外,我应用了编译代码所需的所有更改(即将 :=
更改为 <-
,删除 incr
和 decr
函数等)。
我注意到更改后一些单元测试开始失败。
由于代码非常大,我真的看不出到底发生了什么变化。
这两者的实现是否存在显着差异,可能会改变我的程序的行为?
是的,有区别。引用是 first-class 值,而可变变量是一种语言结构。
或者,从不同的角度来看,您可能会说 ref
单元格是通过引用传递的,而可变变量是通过值传递的。
考虑一下:
type T = { mutable x : int }
type U = { y : int ref }
let t = { x = 5 }
let u = { y = ref 5 }
let mutable xx = t.x
xx <- 10
printfn "%d" t.x // Prints 5
let mutable yy = u.y
yy := 10
printfn "%d" !u.y // Prints 10
发生这种情况是因为 xx
是一个全新的可变变量,与 t.x
无关,因此变异 xx
对 x
没有影响。
但是 yy
是对 与 u.y
完全相同的引用单元格 的引用,因此在引用它时将新值推入该单元格通过 yy
与通过 u.y
.
引用它具有相同的效果
如果您“复制”一个 ref
,副本最终指向同一个 ref
,但如果您复制一个可变变量,则只会复制它的值。
你有区别不是因为一个是 first-value,被 reference/value 或其他东西传递。这是因为 ref
本身就是一个容器 (class)。
当你自己实现一个ref
时,差异会更加明显。你可以这样做:
type Reference<'a> = {
mutable Value: 'a
}
现在看看这两个定义。
type MyTypeA = {
mutable Enabled: bool
}
type MyTypeB = {
Enabled: Reference<bool>
}
MyTypeA
有一个 Enabled
字段可以直接改变或者用其他词是可变的。
在 other-side 上,您有 MyTypeB
理论上是不可变的,但有一个 Enabled 引用了可变的 class。
MyTypeB
中的 Enabled
只是引用了一个可变对象,就像 .NET 中的数百万其他 classes 一样。从上面的类型定义,你可以创建这样的对象。
let t = { MyTypeA.Enabled = true }
let u = { MyTypeB.Enabled = { Value = true }}
创建类型使其更加明显,第一个是可变字段,第二个包含具有可变字段的对象。
您在 FSharp.Core/prim-types.fs 中找到 ref
的实现,它看起来像这样:
[<DebuggerDisplay("{contents}")>]
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpRef`1")>]
type Ref<'T> =
{
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
mutable contents: 'T }
member x.Value
with get() = x.contents
and set v = x.contents <- v
and 'T ref = Ref<'T>
F# 中的 ref
关键字只是创建此类 pre-defined 可变引用对象的 built-in 方法,而不是为此创建您自己的类型。它有一些好处,只要您需要在 .NET 中传递 byref
、in
或 out
值,它就可以很好地工作。所以你应该使用ref
。但是您也可以为此使用 mutable
。例如,两个代码示例的作用相同。
有参考
let parsed =
let result = ref 0
match System.Int32.TryParse("1234", result) with
| true -> result.Value
| false -> result.Value
有一个可变的
let parsed =
let mutable result = 0
match System.Int32.TryParse("1234", &result) with
| true -> result
| false -> result
在这两个示例中,您都会将 1234
作为 int
进行解析。但是第一个示例将创建一个 FSharpRef
并将其传递给 Int32.TryParse
,而第二个示例将创建一个字段或变量并将其与 out
一起传递给 Int32.TryParse
在重构我的 F# 代码时,我发现了一个字段类型为 bool ref
:
type MyType =
{
Enabled : bool ref
// other, irrelevant fields here
}
我决定尝试将其改为 mutable
字段
// Refactored version
type MyType =
{
mutable Enabled : bool
// other fields unchanged
}
此外,我应用了编译代码所需的所有更改(即将 :=
更改为 <-
,删除 incr
和 decr
函数等)。
我注意到更改后一些单元测试开始失败。 由于代码非常大,我真的看不出到底发生了什么变化。
这两者的实现是否存在显着差异,可能会改变我的程序的行为?
是的,有区别。引用是 first-class 值,而可变变量是一种语言结构。
或者,从不同的角度来看,您可能会说 ref
单元格是通过引用传递的,而可变变量是通过值传递的。
考虑一下:
type T = { mutable x : int }
type U = { y : int ref }
let t = { x = 5 }
let u = { y = ref 5 }
let mutable xx = t.x
xx <- 10
printfn "%d" t.x // Prints 5
let mutable yy = u.y
yy := 10
printfn "%d" !u.y // Prints 10
发生这种情况是因为 xx
是一个全新的可变变量,与 t.x
无关,因此变异 xx
对 x
没有影响。
但是 yy
是对 与 u.y
完全相同的引用单元格 的引用,因此在引用它时将新值推入该单元格通过 yy
与通过 u.y
.
如果您“复制”一个 ref
,副本最终指向同一个 ref
,但如果您复制一个可变变量,则只会复制它的值。
你有区别不是因为一个是 first-value,被 reference/value 或其他东西传递。这是因为 ref
本身就是一个容器 (class)。
当你自己实现一个ref
时,差异会更加明显。你可以这样做:
type Reference<'a> = {
mutable Value: 'a
}
现在看看这两个定义。
type MyTypeA = {
mutable Enabled: bool
}
type MyTypeB = {
Enabled: Reference<bool>
}
MyTypeA
有一个 Enabled
字段可以直接改变或者用其他词是可变的。
在 other-side 上,您有 MyTypeB
理论上是不可变的,但有一个 Enabled 引用了可变的 class。
MyTypeB
中的 Enabled
只是引用了一个可变对象,就像 .NET 中的数百万其他 classes 一样。从上面的类型定义,你可以创建这样的对象。
let t = { MyTypeA.Enabled = true }
let u = { MyTypeB.Enabled = { Value = true }}
创建类型使其更加明显,第一个是可变字段,第二个包含具有可变字段的对象。
您在 FSharp.Core/prim-types.fs 中找到 ref
的实现,它看起来像这样:
[<DebuggerDisplay("{contents}")>]
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpRef`1")>]
type Ref<'T> =
{
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
mutable contents: 'T }
member x.Value
with get() = x.contents
and set v = x.contents <- v
and 'T ref = Ref<'T>
F# 中的 ref
关键字只是创建此类 pre-defined 可变引用对象的 built-in 方法,而不是为此创建您自己的类型。它有一些好处,只要您需要在 .NET 中传递 byref
、in
或 out
值,它就可以很好地工作。所以你应该使用ref
。但是您也可以为此使用 mutable
。例如,两个代码示例的作用相同。
有参考
let parsed =
let result = ref 0
match System.Int32.TryParse("1234", result) with
| true -> result.Value
| false -> result.Value
有一个可变的
let parsed =
let mutable result = 0
match System.Int32.TryParse("1234", &result) with
| true -> result
| false -> result
在这两个示例中,您都会将 1234
作为 int
进行解析。但是第一个示例将创建一个 FSharpRef
并将其传递给 Int32.TryParse
,而第二个示例将创建一个字段或变量并将其与 out
一起传递给 Int32.TryParse