【问题标题】:What are the advantages of built-in immutability of F# over C#?F# 相对于 C# 的内置不变性有哪些优势?
【发布时间】:2013-02-26 05:57:09
【问题描述】:
  1. 我听说 F# 原生支持不变性,但它不能在 C# 中复制怎么办?从 C# 不可变数据中获取的 F# 不可变数据有什么用处?

  2. 同样在 F# 中,有没有办法创建可变数据?一切都是不可变的吗?

  3. 如果在应用程序中同时使用 C# 和 F#,是否可以更改 C# 中的不可变 F# 数据?还是只需要创建使用不可变 F# 数据并替换指向这些数据的引用的新 C# 类型?

【问题讨论】:

    标签: c# f# functional-programming immutability


    【解决方案1】:
    1. F# 的工作方式使处理不可变数据变得更加容易,但在这方面,C# 没有什么特别之处。但它可能无法提供简洁的语法。

    2. F# 支持带有 mutable 关键字的可变数据。 F# 不是纯函数式语言。它支持命令式结构,如循环和函数式结构。

    3. 没有。不可变类型实际上是不可变的。 .NET 类型系统在两种语言之间共享。您也不能从 C# 更改不可变数据(当然,缺少反射和内存黑客)。

    【讨论】:

    • 从技术上讲,您可以在 F# 或 C# 中通过反射更改支持不可变属性的(私有)字段 - 如果您的代码有足够的权限来覆盖可访问性检查。
    • @Pavel:确实。我所说的“记忆黑客”是指间接改变事物的广义含义。
    • 谢谢,所以 F# 在 IL 中使用了 C# 理解的新关键字,将值标记为真正不可变?
    • @Joan:不,F# 不使用任何新的 IL 工具。它只是将所有字段设为私有,并且只公开属性获取器而不是它们的设置器。此外,字段本身可能也标记为readonly(不过我没有检查过)。
    【解决方案2】:

    Mehrdad 已经回答了您的所有问题,但只是为了详细说明 #1 - 不同之处在于您需要编写的代码量。例如,下面是 C# 中只读点的定义和示例用法:

    class Point
    {
        private readonly int x, y;
    
        public int X { get { return x; } }
    
        public int Y { get { return y; } }
    
        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
    
    var p = new Point(1, 2);
    

    您可以使用自动属性使其更短一些,但不会对类定义中的代码强制执行不变性,仅对其客户端强制执行不变性。

    在 F# 中,您只需编写:

    type Point = { x: int; y: int }
    let p = { x = 1; y = 2 }
    

    如果你希望它是可变的:

    type Point = { mutable x: int; mutable y: int }
    let p = { x = 1; y = 2 }
    p.x <- 3
    p.y <- 4
    

    【讨论】:

    • 任何时候你通过赋值(可变的东西)改变一个值,你必须使用
    • 因为F#默认是不可变的,所以变异操作符&lt;-不同于赋值操作符=。这可以防止您在无意中意外更改状态,因为您必须明确说明。它还有一个额外的好处是语法笨拙,不鼓励使用非绝对必要的可变值。
    • @Joan:F#(和 OCaml)更喜欢区分为变量赋值 (
    • 你应该可以很容易地使用 Contracts,因为实际的实现是一个 IL 重写器,它不关心用于生成汇编的语言(也就是说,我实际上没有尝试过)。它可以使用 LINQ,但这取决于你的意思。您可以直接在类Enumerable 上调用静态方法,但F# 不会将它们视为扩展方法,也没有C# 中的语法糖。另一方面,F# 有自己的、更丰富的序列操作库 - 请参阅 msdn.microsoft.com/en-us/library/ee353635(VS.100).aspx
    • ... 以及它自己的用于序列理解的语法糖,它实际上相当于匿名迭代器(lambda 中的yield return),如果在 C# 中可能的话。有关这些,请参阅msdn.microsoft.com/en-us/library/dd233209(VS.100).aspx。如果您的意思是 LINQ to SQL - 即在运行时分析语法树并将它们转换为其他东西 - 那么 F# PowerPack 支持。
    【解决方案3】:

    F# 和 C# 在不可变性方面最大的区别在于 F# 默认情况下是不可变的。可以在 C# 中复制所有不可变的构造和集合,但这需要很多 额外的工作。在 F# 中,您可以免费获得它。

    F# 是一种混合语言,并且支持多种形式的可变性。 mutable关键字中最简单的

    let value1 = 42; // immutable
    let mutable value2 = value1; // mutable
    value2 <- 13;  
    

    除了反射技巧,一旦在 F# 中创建数据,无论您使用哪种语言,它都是不可变的。

    【讨论】:

      【解决方案4】:

      正如其他人所说,使用不可变数据有一些重要的好处。仅举几例:

      • 并行化 - 将计算结果并以不可变数据结构返回的程序更容易并行化。
      • 理解代码 - 如果你使用不可变数据编写程序,那么一段代码唯一能做的就是返回一个结果——这使程序更具可读性,因为你可以看到函数/方法的作用分析输入和输出

      从技术上讲,您可以在 C# 和 F# 中编写相同的不可变数据结构。问题是,F# 鼓励您这样做,而在 C# 中编写真正的不可变数据结构有点痛苦。这意味着在编写 F# 代码时,您更有可能编写不可变结构(因为它更容易!)并且只有在您真正需要时才使用可变性(这在 F# 中也是可能的)(这更难!)

      除了默认声明不可变类型外,F# 还有一些不错的功能,可以更轻松地使用它们。例如在处理记录时:

      type MyRect = { X : int; Y : int; Width : int; Height : int }
      let r = { X = 0; Y = 0; Width = 200; Height = 100 }
      
      // You'll often need to create clone with one field changed:
      let rMoved1 = { X = 123; Y = r.Y; Width = r.Width; Height = r.Height }
      
      // The same thing can be written using 'with' keyword:
      let rMoved2 = { r with  X = 123 }
      

      您也可以在 C# 中编写相同的内容,但您需要使用 WithX 方法声明不可变数据结构,该方法返回具有修改的 X 字段、WithY、WithWidth 等的克隆。 . 这是一个不完整的例子:

      class MyRect {
        readonly int x, y, width, height;
      
        MyRect WithX(int newX) {
          return new MyRect(newX, y, width, height);
        }
        // etc...
      }
      

      在没有编译器帮助的情况下,这是很多工作。

      【讨论】:

      • 谢谢,字符串值呢?我听说.NET 中的字符串并不是真正不可变的。 F# 是否使用相同的字符串值?
      • .NET 字符串是真正不可变的。所有使用它们的方法都返回一个新字符串而不修改原始字符串(就像我上面的 WithX 方法一样)。例如 'string s2 = s1.Substring(0, 4)' 返回一个新字符串而不修改 's1'。因此,F# 可以安全地使用与 .NET 相同的字符串类型。
      【解决方案5】:

      你从 F# 不可变对象中得到了什么 不是从 C# 获得的数据 不可变数据?

      F# 允许您轻松地制作不可变数据的修改副本,这从 C# 开始非常繁琐且耗时。考虑在 F# 中创建记录:

      type Person = { FirstName: string; MiddleName: string; LastName: string }
      let me = { FirstName = "Robert"; MiddleName = "Frederick"; LastName = "Pickering" }
      

      假设我想更改姓氏字段,在 F# 中,我可以通过使用“with”关键字进行复制来做到这一点:

      let me' = { me with LastName = "Townson" }
      

      如果您想在 C# 中执行此操作,则必须创建将每个字段复制到新结构中的方法,并且很有可能将字段混淆。

      集合还有其他合适的优势。例如 C# 支持不可变的 ReadOnlyCollection。但是,每次您想向此集合添加新项目时,您都必须制作整个集合的副本。 F# 内置列表允许您只将新项目添加到列表末尾而不复制它,即新生成的列表实际上将共享大部分旧列表。即:

      let myList = [3; 2; 1]
      let myList' =  4 :: myList
      

      创建“myList”时,整个“myList”保持不变,并在其头部添加“4”以创建“myList”。

      【讨论】:

      • 谢谢。你在 let myList 之后使用的 ' 符号是什么?
      • 是单引号,只是标识符名称的一部分。在 F# 中,它经常用于代替主要符号 en.wikipedia.org/wiki/Prime_(symbol),表示先前声明的标识符的更改版本。
      【解决方案6】:

      我认为您是从一个过于详细的功能角度来看这个问题。

      我认为函数式编程语言默认不变性的主要优势在于它如何影响开发人员的实际编程思维。当突变是默认设置时,编程思维定势通过改变数据来寻求解决方案。当不变性是默认设置时,编程思维模式倾向于通过转换数据来解决问题。

      详细说明为什么后一种思维方式更好。在通过改变数据来开发程序时,您最终会陷入细节的困境,因此您的重点往往是程序如何“从小”工作。通过转换数据开发程序时,您无需担心许多细节(例如程序的不同变异部分如何相互交互),您的重点往往是程序如何工作大'。因此,后一种思维方式使开发人员能够更好、更清晰地了解程序的工作原理。

      虽然有一个干净的程序“大图”很好,但在某些情况下,现实生活中的担忧可能超过它。一些数据结构(数组、多维数组)由于制作新副本的成本很高,因此不是很好的不可变性候选者,因此即使使用函数式语言,也应始终应用一些常识来判断何时不使用不可变性。

      编辑以回答评论:

      函数式语言纯粹主义者会争辩说,这会使代码更丑陋。我认为自己不是一个纯粹主义者,所以我不这么认为;好吧,从理论上讲,也许更丑。

      F# 是一种不纯的函数式编程语言,因此它在需要时支持可变性。让你的部分状态是可变的并不会使你的大部分状态不可变的好处失效。

      在大多数情况下,您最终会使用不同的(纯)数据结构,而您通常会在命令式语言中使用数组。例如。元组、列表或树(或在树上构建的集合/映射;标准库通常带有具有良好时间复杂度的实现)。

      如果你认为你真的需要一个可变数组(出于性能原因),你可以使用它,所以没有什么能阻止你使用它。

      【讨论】:

      • 那么你如何处理像使用数组这样的问题,就像你说的那样每次都制作一个新副本的成本很高。它是否使 F# 代码在这些部分更丑陋?任何例子都会很有帮助。
      • Joan:通常不会让消费代码更丑;看起来就像let newarr = mangle(oldarr)。尽管每次制作新副本的成本很高,但可以设计数据结构以通过共享结构来利用不变性;例如,如果您有链表 A = (3, 2, 1) 和链表 B = (4, 3, 2, 1),并且两者都是不可变的,则链表 B 可以用 4 表示,然后是指向头部的指针的 A。同样的事情可以用树来做。
      • 请注意,显然,如果 A 是可变的,则无法使用该技巧,因为如果更改 A,任何引用 B 的人都会发现 B 也不同。
      【解决方案7】:

      你从 F# 不可变数据中得到什么,而你却没有从 C# 不可变数据中得到什么?

      第一个好处是 F# 函数除了返回其结果之外没有任何其他效果。不会更改全局数据,也不会依赖可变数据,因此您可以保证,只要您坚持使用不可变数据,相同的调用每次都会可靠地产生相同的结果。

      这第一个好处本身就已经很好了,因为它使您的代码更容易理解,但是大的好处是因为F#函数可以安全地组合。通过函数组合进行编程被证明是一种非常强大的模块化方式,可以使用可重用的部分进行编程。而且您可以构建真正强大的部分,这些部分可以以多种不同的方式组合在一起,因此您可以用更少的代码编写更多的程序。

      John Hughes 的标志性论文Why Functional Programming Matters 对这些想法的解释比我以往任何时候都好。 John 解释了移除变异能力实际上如何为语言整体增加了表达能力。阅读论文!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-09-16
        • 2010-10-27
        • 2015-03-28
        • 1970-01-01
        • 1970-01-01
        • 2013-09-22
        • 2012-05-17
        • 1970-01-01
        相关资源
        最近更新 更多