为什么不可变编程比可变编程更受欢迎?
why immutable programming is preferred than mutable?
我从所有关于这个话题的 available article 中得到的是,
不可变对象的主要优点是可以进行并发编程。
所以如果任何应用程序使用这么多线程,那么我们可以使用不变性。
这里我的问题是:如果应用程序不需要多线程,那么不可变编程仍然是首选吗?因为为每个更改创建对象的新副本可能非常昂贵。(如果是,为什么)
考虑一下:
class Time(t: Double) {
private var time = t // Danger: mutable field
def getTime = time
def setTime(t: Double): Unit = time = t
}
val startAt = new Time(5.0)
val someOtherTime = startAt
someOtherTime.setTime(10.0)
// What does startAt.getTime return?
在这个简单的示例中,您可能(或可能不会)惊讶地发现,在 someOtherTime.setTime(10.0)
returns 之后,startAt.getTime
的值将改为 10.0
5.0
个。您是否预料到了,特别是考虑到 startAt
是 val
?
鉴于您已经看过代码,这也许是意料之中的事情。但是,如果您只是使用 Time
而没有查看其内部结构,我猜您会对这种行为感到非常惊讶。可变对象的状态可能会在您不知情的情况下发生变化 - 特别是在多线程环境中,其中对 Time.time
的访问必须同步 - 但即使是单线程情况也会引入意想不到的复杂性。
保持对象不可变使它们易于推理。当这些对象表示 值类型 时,不变性实际上是必不可少的。 (在任何主要编程语言或库中命名一个不是不可变的单一值类型。)
关于单线程与多线程编码的进一步说明:如果您正在编写单线程应用程序,您可能会试图完全忽略线程安全问题。但是,我认为在一般情况下这有点短视。所有新处理器都有多个内核,并且有迹象表明处理器将在未来几年获得越来越多的内核。如果您想编写响应迅速的高性能代码,您将需要使用线程安全库编写多线程应用程序。
如果您编写的代码不是线程安全的,并且如果它包含大量表现出共享可变状态的可变对象,那么您将不得不这样做大量重构使其适用于多线程应用程序。另一方面,如果您的对象是不可变的,并且没有共享的可变状态,那么这项工作将会容易得多。
顺便说一句,完全可以在函数中使用可变状态,class,等等,前提是可变状态不共享(即外部可见)。这样您就可以享受可变状态性能的好处,而没有缺点。
我完全同意不可变对象更易于推理,即使在单线程程序中也是如此。然而,有时使用可变数据更实用,例如,如果性能至关重要(我们的计算机针对可变操作进行了优化)或者您使用的库本身就是有状态的。
我还要补充一点,Scala 并不是编写纯函数式程序的最佳语言。 Haskell、PureScript、Idris 等语言更适合恕我直言的任务。对于一些多线程程序来说,以纯函数式的方式编写 Scala 程序绝对是值得的。
我从所有关于这个话题的 available article 中得到的是,
不可变对象的主要优点是可以进行并发编程。 所以如果任何应用程序使用这么多线程,那么我们可以使用不变性。
这里我的问题是:如果应用程序不需要多线程,那么不可变编程仍然是首选吗?因为为每个更改创建对象的新副本可能非常昂贵。(如果是,为什么)
考虑一下:
class Time(t: Double) {
private var time = t // Danger: mutable field
def getTime = time
def setTime(t: Double): Unit = time = t
}
val startAt = new Time(5.0)
val someOtherTime = startAt
someOtherTime.setTime(10.0)
// What does startAt.getTime return?
在这个简单的示例中,您可能(或可能不会)惊讶地发现,在 someOtherTime.setTime(10.0)
returns 之后,startAt.getTime
的值将改为 10.0
5.0
个。您是否预料到了,特别是考虑到 startAt
是 val
?
鉴于您已经看过代码,这也许是意料之中的事情。但是,如果您只是使用 Time
而没有查看其内部结构,我猜您会对这种行为感到非常惊讶。可变对象的状态可能会在您不知情的情况下发生变化 - 特别是在多线程环境中,其中对 Time.time
的访问必须同步 - 但即使是单线程情况也会引入意想不到的复杂性。
保持对象不可变使它们易于推理。当这些对象表示 值类型 时,不变性实际上是必不可少的。 (在任何主要编程语言或库中命名一个不是不可变的单一值类型。)
关于单线程与多线程编码的进一步说明:如果您正在编写单线程应用程序,您可能会试图完全忽略线程安全问题。但是,我认为在一般情况下这有点短视。所有新处理器都有多个内核,并且有迹象表明处理器将在未来几年获得越来越多的内核。如果您想编写响应迅速的高性能代码,您将需要使用线程安全库编写多线程应用程序。
如果您编写的代码不是线程安全的,并且如果它包含大量表现出共享可变状态的可变对象,那么您将不得不这样做大量重构使其适用于多线程应用程序。另一方面,如果您的对象是不可变的,并且没有共享的可变状态,那么这项工作将会容易得多。
顺便说一句,完全可以在函数中使用可变状态,class,等等,前提是可变状态不共享(即外部可见)。这样您就可以享受可变状态性能的好处,而没有缺点。
我完全同意不可变对象更易于推理,即使在单线程程序中也是如此。然而,有时使用可变数据更实用,例如,如果性能至关重要(我们的计算机针对可变操作进行了优化)或者您使用的库本身就是有状态的。
我还要补充一点,Scala 并不是编写纯函数式程序的最佳语言。 Haskell、PureScript、Idris 等语言更适合恕我直言的任务。对于一些多线程程序来说,以纯函数式的方式编写 Scala 程序绝对是值得的。