不可变结构比可变结构有什么好处?

What are the benefits of an immutable struct over a mutable one?

我已经知道不变性相对于可变性的好处在于能够推理代码并引入更少的错误,尤其是在多线程代码中。但是,在创建结构时,我看不出有什么比创建完全不可变的结构优于可变结构的好处。

让我们举一个保持分数的结构的例子:

struct ScoreKeeper {
    var score: Int
}

在此结构中,我可以更改现有结构变量的分数值

var scoreKeeper = ScoreKeeper(score: 0)
scoreKeeper.score += 5
println(scoreKeeper.score)
// prints 5

不可变版本如下所示:

struct ScoreKeeper {
    let score: Int

    func incrementScoreBy(points: Int) -> ScoreKeeper {
        return ScoreKeeper(score: self.score + points)
    }
}

及其用法:

let scoreKeeper = ScoreKeeper(score: 0)
let newScoreKeeper = scoreKeeper.incrementScoreBy(5)
println(newScoreKeeper.score)
// prints 5

我没有看到第二种方法比第一种方法有什么好处,因为结构是值类型。如果我传递一个结构,它总是被复制。因此,如果结构具有可变 属性,对我来说似乎并不重要,因为代码的其他部分无论如何都会在单独的副本上工作,从而消除了可变性问题。

虽然我看到有些人使用第二个示例,但它需要更多代码而没有明显的好处。有什么我没有看到的好处吗?

很好的分析,特别指出结构是按值传递的,因此不会被其他进程更改。

我能看到的唯一好处是通过使元素的不可变性显式化在风格上。

在面向对象的风格中,使基于值的类型与基于对象的类型同等对待更像是一种风格。这更多是个人选择,我看不出它们有什么大的好处。

一般而言,不可变对象对系统的成本低于可变对象。可变对象需要具有接受新值的基础设施,并且系统必须考虑到它们的值可以随时更改的事实。

可变对象在并发代码中也是一个挑战,因为您必须防止从另一个线程中更改值。

但是,如果您不断地创建和销毁唯一的不可变对象,那么创建新对象的开销会很快变得昂贵。

在基础 classes 中,NSNumber 是一个不可变对象。系统维护一个你之前使用过的 NSNumber 对象池,如果你要求一个与你之前创建的值相同的值,系统会在幕后返回一个现有的数字。

那是我唯一可以看到使用静态结构的价值的情况 - 它们不会发生太大变化,并且您的可能值池很小。在那种情况下,您可能希望使用 "factory method" 设置 class,它保留最近使用的结构并在您再次请求具有相同值的结构时重用它们。

如上所述,这样的方案可以简化并发代码。在那种情况下,您不必防止在另一个线程中更改结构的值。如果您使用这样的结构,您就会知道它永远不会改变。

您关于复制值类型的评论非常好。也许这在特定语言(swift)和特定编译器实现(当前版本)中没有多大意义,但通常如果编译器确定数据结构是不可变的,它可以例如在幕后使用参考而不是副本来获得一些性能改进。由于显而易见的原因,这不能用可变类型来完成。

更一般地说,限制意味着信息。如果你以某种方式限制你的数据结构,你会获得一些关于它的额外知识。额外的知识意味着更多的可能性;)也许当前的编译器没有利用它们,但这并不意味着它们不在这里 :)

不同的方法将有助于对代码进行不同类型的更改。不可变结构与不可变 class 对象非常相似,但可变结构和可变 class 对象非常不同。因此,如果出于某种原因需要使用 class 对象来代替,使用不可变结构的代码通常可以很容易地进行调整。

另一方面,使用不可变对象通常会使用修改后的版本替换变量的代码更加脆弱,以防向相关类型添加其他属性。例如,如果 PhoneNumber 类型包括 AreaCode、LocalExchange 和 LocalNumber 的方法以及采用这些参数的构造函数,然后为扩展添加 "optional" 第四个 属性,则代码为应该通过将新区号 LocalExchange 和 LocalNumber 传递给三参数构造函数来更改某些 phone 号码的区号,这将擦除每个 phone 号码的分机号 属性,而可以直接写入 AreaCode 的代码不会有这个问题。