F#:让可变与引用


82

首先,我承认这个问题可能是重复的;让我知道。

我很好奇在需要可变性的情况下通常的“最佳实践”是什么。F#为此提供了两种功能:let mutable绑定(似乎在“大多数”语言中像变量一样工作ref)以及需要显式取消引用才能使用的引用单元格(通过函数创建)。

有一对夫妇的情况下,其中一个是“被迫”转化为一种或另一种:.NET互操作倾向于使用可变带<-,并在工作流程的计算必须使用ref:=。因此,这些情况非常明确,但是我很好奇在这些情况之外创建自己的可变变量时该怎么做。一种风格比另一种有什么优势?(也许对实现有进一步的了解会有所帮助。)

谢谢!



4
请注意,在F#版本4中,可以在需要引用的地方使用mutable。 blogs.msdn.com/b/fsharpteam/archive/2014/11/12/…–
James Moore

Answers:


133

我只能支持gradbot所说的-当我需要突变时,我更喜欢let mutable

关于实现和两者之间的区别-ref单元本质上是通过一个包含可变记录字段的非常简单的记录来实现的。您可以自己轻松地编写它们:

type ref<'T> =  // '
  { mutable value : 'T } // '

// the ref function, ! and := operators look like this:
let (!) (a:ref<_>) = a.value
let (:=) (a:ref<_>) v = a.value <- v
let ref v = { value = v }

两种方法之间的显着区别是,let mutable将可变值存储在堆栈上(作为C#中的可变变量),而ref将可变值存储在堆分配记录的字段中。这可能会影响性能,但是我没有任何数字...

因此,ref可以为使用的可变值加上别名-这意味着您可以创建两个引用相同可变值的值:

let a = ref 5  // allocates a new record on the heap
let b = a      // b references the same record
b := 10        // modifies the value of 'a' as well!

let mutable a = 5 // mutable value on the stack
let mutable b = a // new mutable value initialized to current value of 'a'
b <- 10           // modifies the value of 'b' only!

2
提醒一下:放在堆栈上还是堆上是实现细节,与问题不完全相关(尽管如此,答案很好)
Bruno Brant 2014年

5
我会说,在确定最佳实践是什么时,了解是否会引起堆分配和收集的开销非常重要。
jackmott

@jackmott查看Eric Lippert的这篇文章,该文章叫做《堆栈是实现细节》
jaromey

5
@jaromey是的,我了解我从根本上完全反对eric。在考虑什么是“最佳实践”时,您不能忽略性能方面的考虑。人们通常认为性能无关紧要,但由于千个实施细节导致的运行速度太慢,导致软件运行缓慢。
jackmott

18

相关问题: “您提到闭包无法捕获局部可变值,因此您需要使用ref。原因是闭包中捕获的可变值需要分配在堆上(因为闭包是在堆)。” 从F#ref-mutable vars与对象字段

我认为let mutable比参考单元格更可取。我个人仅在需要时使用参考单元格。

由于递归和尾部调用,我编写的大多数代码都不使用可变变量。如果我有一组可变数据,则使用一条记录。对于对象,我使用let mutable了私有可变变量。我只真正使用引用单元格来关闭,通常是事件。



4

Brian的这篇文章可能会提供答案。

变量易于使用且高效(无需包装),但不能在lambda中捕获。Ref单元格可以捕获,但冗长且效率较低(?-不确定)。


“(...)效率较低”-可能是包装器类型占用更多内存。
JMCF125

2
这已经发生了变化,因为在大多数情况下都可以捕获F#4.0可变的,并且对ref的需求现在要少得多。
亚伯(Abel)

3

您可能需要看一下Wikibook中的Mutable Data部分。

为了方便起见,以下是一些相关的引号:

mutable关键字经常与记录类型一起使用以创建可变记录

可变变量在某种程度上受到限制:可变变量在定义它们的函数范围之外不可访问。具体来说,这意味着不可能引用另一个函数的子函数中的可变对象。

Ref细胞绕过了一些可变因素的局限。实际上,ref单元是非常简单的数据类型,它将可变字段包装成记录类型。

由于ref单元是在堆上分配的,因此它们可以在多个函数之间共享

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.