为什么在F#中允许使用mutable?他们何时必不可少?

FBr*_*t87 6 f#

来自C#,试图了解语言.

根据我的理解,F#的一个主要好处是你放弃了状态的概念,这应该(在许多情况下)使事情更加健壮.

如果是这种情况(并纠正我,如果不是这样),为什么允许我们用变量来打破这个原则?对我来说,感觉它们不属于语言.我知道你不必使用它们,但是它为你提供了偏离轨道并以OOP方式思考的工具.

任何人都可以提供一个可变值必不可少的例子吗?

Van*_*oiy 8

当前用于声明(无状态)代码的编译器不是很聪明.这导致大量的内存分配和复制操作,这相当昂贵.改变对象的某些属性允许以新状态重用对象,这样会快得多.

想象一下,你创造了一个拥有10000个单位的游戏,每秒60个刻度.您可以在F#中执行此操作,包括在单个CPU核心上与可变四叉或八叉树的冲突.

现在想象单位和四叉树是不可变的.编译器没有比每秒分配和构造600000个单元并且每秒创建60个新树更好的想法.这排除了其他管理结构的任何变化.在具有复杂单元的实际用例中,这种解决方案将太慢.

F#是一种多范式语言,使程序员能够编写功能性,面向对象的程序,并在某种程度上编写命令式程序.目前,每种变体都有其有效用途.也许,在未来的某个时刻,更好的编译器将允许更好地优化声明性程序,但是现在,当性能成为一个问题时,我们必须回到命令式编程.


lat*_*kin 7

除了其他方面,具有使用可变状态的能力通常对性能原因很重要.

考虑实现API List.take: count : int -> list : 'a list -> 'a list,该API 返回仅包含count输入列表中的第一个元素的列表.

如果您受到不变性的约束,列表只能从前到后构建.实施take然后归结为

  • count输入中的第一个人一起构建结果列表:O(计数)
  • 反转该结果并返回O(计数)

出于性能原因,F#运行时具有神奇的特殊能力,可以在需要时从前到后构建列表(即,将最后一个人的尾部变为指向新的尾部元素).用于的基本算法List.take是:

  • count从输入中的第一个人前后建立结果列表:O(计数)
  • 返回结果

相同的渐近性能,但实际上,在这种情况下使用变异的速度是其两倍.

由于代码难以推理,普遍可变状态可能是一场噩梦.但是如果你考虑你的代码使得可变状态被紧密封装(例如在实现细节中List.take),那么你可以在有意义的地方享受它的好处.因此,将不可变性作为默认值,但仍然允许可变性,是该语言非常实用且有用的特性.


Dan*_*ian 6

首先,在我看来,使F#强大的原因不仅仅是默认的不变性,而是整个功能组合,例如:默认的不变性,类型推断,轻量级语法,总和(DUs)和产品类型(元组),默认情况下模式匹配和currying.可能更多.

这些使得F#在默认情况下非常实用,它们会让你以某种方式编程.特别是当你使用可变状态时,它们会让你感到不舒服,因为它需要mutable关键字.这种意义上的不舒服意味着更加小心.这正是你应该做的.

可变的状态并不被禁止或邪恶本身,但应控制.明确使用的需要mutable就像一个警告标志,让你意识到危险.如何控制它的好方法是在函数内部使用它.这样你就可以拥有自己的内部可变状态,并且仍然是完全线程安全的,因为你没有共享的可变状态.实际上,即使内部使用可变状态,您的函数仍然可以是引用透明的.

至于为什么F#允许可变状态; 如果没有它的可能性,编写通常的现实代码是非常困难的.例如在Haskell中,类似随机数的东西不能像在F#中那样完成,而是需要明确地通过状态进行线程化.

当我编写应用程序时,我倾向于将大约95%的代码库放在一个非常实用的样式中,只需1:1便携式就可以毫不费力地说Haskell.但是在系统边界或某些性能关键的内部循环中使用可变状态.这样你就可以获得两全其美.