我知道Scala完全拥抱不变性。
现在我在想一个场景,我必须在类等中保持一些状态(通过变量)。我稍后需要更新这些变量;然后我可以稍后重新访问该类以访问更新的变量。
我将尝试用一个非常简单的例子来简化:
class A {
var x: Int
def compute: Int = {calling some other processes or such using x as input}
}
...
def invoker() {
val a: A = new A
a.x = 1
......
val res1 = a.compute
a.x = 5
......
val res2 = a.compute
......
}
所以你看,我需要不断改变x并获得结果。如果你认为我可以简单地将x作为计算的参数,例如
def compute(x: Int)
......
这是一个好主意,但我无法在我的情况下这样做,因为我需要分离x的设置值并完全计算结果。换句话说,设置x值不应该触发“计算”发生,相反,我需要能够在程序中随时设置x值,并且能够在我需要时在程序中的任何其他时间重用该值进行计算
在这种情况下,我使用的是变量(var x:Int)。这是合法的还是仍然有一些不可改变的方式来处理它?</ p>
答案 0 :(得分:0)
无论何时存储状态,您都需要使用可变性。
在您的情况下,您希望存储 x 并单独计算。本质上,这意味着状态是必需的,因为计算的结果取决于 x 的状态
如果你真的希望 compute 的类是不可变的,那么其他一些可变类需要包含 x ,它需要传递给compute方法
答案 1 :(得分:0)
相反,我需要能够在程序中随时设置x值,并且能够在需要时在程序中的任何其他时间重用该值。
然后,根据定义,您希望您的班级是有状态的。你可以重构你的问题,以便特定的班级不需要国家,但是,这是否有用和/或值得麻烦是你必须要弄清楚的。
答案 2 :(得分:0)
您的模式在ListBuffer
中使用(例如size
作为compute
功能)。
所以是的,有些情况下你可以使用这种模式。例如:
val l = List(1, 2, 3)
val lb = new ListBuffer[Int]
l.foreach(n => lb += n * n)
val result = lb.toList
println(result)
另一方面,缓冲区通常仅用于尽快创建不可变实例。如果您查看此代码,有两个项可能表明它可以更改:可变缓冲区和foreach
(因为foreach
仅为其副作用调用)
另一个选择是
val l = List(1, 2, 3)
val result = l.map(n => n * n)
println(result)
在更少的行中也是如此。我更喜欢这种风格,因为你只是在看不可变实例和“功能”功能。
在您的抽象示例中,您可以尝试将可变状态和函数分开:
class X(var i: Int)
class A {
def compute(x: X): Int = { ... }
}
甚至可能
class X(val i: Int)
这种方式compute
变得有效:它的返回值仅取决于参数。
我个人最喜欢的“意外”不可变课程是scala.collection.immutable.Queue
。有了“命令性”背景,你不要指望队列是不可变的。
因此,如果你看一下你的模式,很可能你可以把它变成不可变的。
答案 3 :(得分:0)
我会创建一个不可变的A类(这里是一个case类),让一个对象处理可变性。对于每个状态更改,我们创建一个新的A对象并更改对象中的引用。如果从不同的线程设置x,那么处理并发性就更好了,你只需要使变量成为volatile或AtomicReference。
object A {
private[this] var a = A(0)
def setX(x: Int) { if (x != a.x) a = new A(x) }
def getA: A = a
}
case class A(x: Int) {
def compute: Int = { /*do your stuff*/ }
}
答案 4 :(得分:0)
经过几个月的函数式编程,这是我的反思。
每次修改/更改/更新/变异变量时,处理此变量的必要方法是使用该变量记录此类变更。功能性思维方式是使活动(导致变化)为您带来新的状态。换句话说,它就像是因果效应。功能性思维方式侧重于因果关系的过渡活动。
鉴于这一切,在程序执行的任何给定时间点,我们的成就是中间结果。无论我们如何做,我们都需要在某处保持结果。这样的中间结果是状态,是的,我们需要一些变量来保持它。这就是我想用抽象思维分享的东西。