F# 记录:ref 与可变字段

F# record: ref vs mutable field

提问人:LA.27 提问时间:2/12/2022 更新时间:2/12/2022 访问量:305

问:

在重构我的 F# 代码时,我发现了一条字段类型为 :bool ref

type MyType =
  {
    Enabled : bool ref
    // other, irrelevant fields here
  }

我决定尝试将其更改为字段mutable

// Refactored version
type MyType =
  {
    mutable Enabled : bool
    // other fields unchanged
  }

此外,我还应用了编译代码所需的所有更改(即更改为 、删除和函数等)。:=<-incrdecr

我注意到,在更改之后,一些单元测试开始失败。 由于代码非常大,我真的看不出到底发生了什么变化。

两者的实现是否存在显著差异,可能会改变我的程序的行为?

数据结构 f# 可变

评论

0赞 Phillip Carter 2/12/2022
如果不知道您的测试是什么样的,就不知道会发生什么。
0赞 LA.27 2/12/2022
在这里复制粘贴几百行代码是很困难的。这就是为什么我决定保持示例简单,并要求两个实现之间的差异,这可以给我一个想法。
0赞 Phillip Carter 2/12/2022
因此,在较高级别上,F# 中的可变值存储在堆栈中,除非它们被捕获,此时它们将转换为引用单元格。Ref 单元格是堆上的对象。但除了语法变化之外,仅此而已。

答:

6赞 Fyodor Soikin 2/12/2022 #1

是的,有区别。引用是一流的值,而可变变量是一种语言结构。

或者,从另一个角度来看,您可能会说单元格是通过引用传递的,而可变变量是通过值传递的。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

发生这种情况是因为是一个全新的可变变量,与 无关,因此突变对 没有影响。xxt.xxxx

but 是对与 完全相同的 ref 单元格的引用,因此在引用 via 的同时将新值推送到该单元格中,其效果与通过 引用它具有相同的效果。yyu.yyyu.y

如果你 “复制” a ,则该副本最终指向相同的变量,但如果你复制一个可变变量,则只会复制其值。refref

评论

0赞 LA.27 2/12/2022
我明白了,有道理。因此,最有可能在我的代码中的某个地方,我复制了该字段,对其进行了修改,然后在版本中丢弃了更改。谢谢,我去看看。mutable
2赞 David Raab 2/12/2022 #2

你有区别不是因为一个是第一值,通过引用/值或其他东西传递。这是因为 a 本身只是一个容器(类)。ref

当您自己实现时,差异会更加明显。你可以这样做:ref

type Reference<'a> = {
    mutable Value: 'a
}

现在看看这两个定义。

type MyTypeA = {
    mutable Enabled: bool
}

type MyTypeB = {
    Enabled: Reference<bool>
}

MyTypeA有一个可以直接更改的字段,或者与其他单词是可变的。Enabled

另一方面,你有理论上不可变的,但有一个 Enabled,它引用了一个可变类。MyTypeB

from 只是对一个对象的引用,该对象是可变的,就像 .NET 中的数百万个其他类一样。从上面的类型定义中,您可以创建这样的对象。EnabledMyTypeB

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# 中的关键字只是创建此类预定义可变 Reference 对象的内置方法,而不是为此创建自己的类型。它有一些好处,每当您需要在 .NET 中传递 或值时,它都能很好地工作。所以你应该使用 .但你也可以为此使用 a。例如,两个代码示例执行相同的操作。refbyrefinoutrefmutable

有参考

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

在这两个示例中,您都会得到一个解析。但是第一个示例将创建一个并将其传递给,而第二个示例将创建一个字段或变量并将其传递给1234intFSharpRefInt32.TryParseoutInt32.TryParse