【问题标题】:Scala: var List vs val MutableListScala:var List 与 val MutableList
【发布时间】:2012-06-15 11:23:50
【问题描述】:

在 Odersky 等人的 Scala 书中,他们说使用列表。我没有读完这本书的封面,但所有的例子似乎都使用了 val List。据我了解,还鼓励使用 vals 而不是 vars。但在大多数应用程序中,使用 var List 或 val MutableList 之间没有权衡吗?显然,我们尽可能使用 val List。 但是使用大量的 var 列表(或 var 向量等)是一种好习惯吗?

我对来自 C# 的 Scala 还是很陌生。我有很多:

public List<T> myList {get; private set;}

如果 C# 内置了不可变性,则很容易声明为 val 的集合,因为集合本身在构造后从未更改,即使在其生命周期中会从集合中添加和减去元素。因此,声明一个 var 集合几乎感觉像是离不变性迈出了一步。

针对答案和 cmets,Scala 的一大卖点是:它可以带来很多好处,而无需像 Lisp 或 Haskell 那样完全改变编写代码的方式。

【问题讨论】:

  • 不使用可变列表有什么好处?我可以看到您如何获得线程安全,但您会失去什么?
  • @DonStewart,您可能会不小心将集合变量设置为错误的值。尽管不是一成不变的,但 C# 文化似乎更加强调私有性,这减少了这种类型的运行时错误。但是对于消费者来说,val 总是比 var 更可取,当然所有事情都是平等的。
  • @RichOliver 我相信@DonStewart 的意思是不使用可变列表不同时使用vars。
  • @ErikAllik 我的问题不是重复的,因为它首先被问到了。另一个问题可能与我的重复。

标签: scala collections immutability


【解决方案1】:

我相信你不能将可变val/不可变var的问题与具体用例分开。让我再深入一点:你想问自己两个问题:

  1. 我如何将我的收藏暴露在外面?
    1. 我想要当前状态的快照,并且无论对托管集合的实体所做的更改如何,此快照都不应更改。在这种情况下,您应该更喜欢不可变的var。原因是使用可变 val 的唯一方法是通过防御性副本。
    2. 我想要一个关于状态的视图,它应该改变以反映对原始对象状态的更改。在这种情况下,您应该选择不可变的val,并通过不可修改的包装器返回它(就像Collections.unmodifiableList() 在Java 中所做的那样,here 是一个问题,它被问到如何在Scala 中这样做)。我认为没有办法使用不可变的 var 来实现这一点,所以我相信您在这里的选择是强制性的。
    3. 我只关心没有副作用。在这种情况下,这两种方法非常相似。使用不可变的var,您可以直接返回内部表示,因此可能会更清晰一些。
  2. 如何修改我的收藏?
    1. 我通常会进行批量更改,即一次设置整个集合。这使得不可变的var 成为更好的选择:您只需将输入中的内容分配给您当前的状态,就可以了。使用不可变的val,您需要先清除您的收藏,然后将新内容复制进去。肯定更糟。
    2. 我通常进行逐点更改,即,我在集合中添加/删除单个元素(或其中的几个)。这是我大多数时候实际看到的,集合只是一个实现细节,trait 仅公开用于逐点操作其状态的方法。在这种情况下,可变的val 通常在性能方面可能会更好,但如果这是一个问题,我建议查看Scala's collections performance

【讨论】:

    【解决方案2】:

    使用大量 var 列表(或 var 向量)是一种好习惯吗? 等)?

    我想说将var 用于不可变集合比将val 用于可变集合更好。在我的脑海中,因为

    • 你对行为有更多保证:如果你的对象有一个可变列表,你永远不知道其他外部对象是否会更新它

    • 您限制了可变性的范围;返回集合的方法将产生一个不可变的集合,因此您的一个对象内只有可变性

    • 通过简单地将 var 分配给 val 来使 var 不可变很容易,而要使可变集合不可变,您必须使用不同的集合类型并重新构建它

    在某些情况下,例如具有大量 I/O 的时间相关应用程序,最简单的解决方案是使用可变状态。在some 的情况下,可变的解决方案更加优雅。但是,在大多数代码中,您根本不需要可变性。关键是使用具有更高阶函数的集合而不是循环,或者如果不存在合适的函数则使用递归。这比听起来简单。您只需要花一些时间了解 List (以及其他集合,它们大多相同)上的方法。最重要的是:

    map:将您提供的函数应用于集合中的每个元素 - 使用而不是循环和更新数组中的值

    foldLeft:从集合中返回单个结果 - 使用而不是循环和更新累加器变量

    for-yield 表达式:简化映射和过滤,尤其是针对嵌套循环类型的问题

    归根结底,大部分函数式编程都是不变性的结果,你不能真正拥有一个没有另一个;然而,局部变量主要是一个实现细节:只要它不能脱离局部范围,一点可变性就没有错。因此,请使用带有不可变集合的 var,因为本地 var 不会被导出。

    【讨论】:

    • 嗯,是的,这似乎是一个不错的答案。我不是在寻找最终的解决方案,而是在寻找一种可行的默认做法。它节省了导入可变集合。所以它似乎与语言颗粒有关。当我有一个更好的整体视图时,我准备在以后进行重大重构。
    【解决方案3】:

    以下是我如何看待函数式编程中的可变性问题。

    最佳解决方案:值是最好的,所以函数式编程中最好的用法是值和递归函数:

    val myList = func(4); 
    def func(n) = if (n>0) n::func(n) else Nil
    

    需要可变的东西:有时需要可变的东西,或者让一切变得更容易。当我们遇到这种情况时,我的印象是使用可变结构,所以使用val list: collection.mutable.LinkedList[T]而不是var list: List[T],这不是因为性能的真正改进,而是因为可变集合中已经定义的可变函数。

    这个建议是个人的,当你想要性能时可能不推荐,但它是我在 scala 中用于日常编程的指南。

    【讨论】:

      【解决方案4】:

      如果有必要使用 var 列表,为什么不呢?为了避免问题,您可以例如限制变量的范围。前段时间有一个类似的问题,答案很好:scala's mutable and immutable set when to use val and var

      【讨论】:

      • 作为“通常是最佳选择”的答案是有意义的。就开发人员的时间而言,这与说没有必要或实际上是谨慎的妥协是非常不同的。
      【解决方案5】:

      您假设List 必须是可变的,或者指向它的任何内容都必须是可变的。换句话说,您需要选择以下两个选项之一:

      val list: collection.mutable.LinkedList[T]
      var list: List[T]
      

      这是错误的二分法。你可以同时拥有:

      val list: List[T]
      

      所以,您应该问的问题是我该怎么做?,但我们只能在您尝试并面临特定问题时回答。没有通用的答案可以解决你所有的问题(嗯,有——单子和递归——但那通用了)。

      所以...试试看。您可能有兴趣查看Code Review,其中大多数 Scala 问题都与如何使某些代码更具功能性有关。但是,归根结底,我认为最好的方法是尝试,当你无法解决某些具体问题时,就求助于 Stack Overflow。

      【讨论】:

      • 目前我觉得这是一个真正的二分法,因为尽管有兴趣进一步探索函数式方法,但在不久的将来我想专注于更全面地理解 Scala 语法。 目前我只想要低垂的功能性果实。 C# 类型系统的局限性是我转向 Scala 的驱动力,而不是缺乏功能性。我仍然知之甚少,但看 Eric Meijer 的一些东西增强了我对功能性原教旨主义的本能警惕。
      • @RichOliver 在这种情况下,最容易实现的目标是引用透明度。这意味着外部观察者将无法观察状态,这通常更容易使用 var 作为包含不可变集合的方法局部变量来实现。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2017-04-02
      相关资源
      最近更新 更多