用副作用包装 class

Wrapping a class with side-effects

在阅读了 Functional Programming in Scala 的第六章并试图理解 State monad 之后,我有一个关于包装副作用的问题 class。

假设我有一个 class 以某种方式自我修改。

class SideEffect(x:Int) {
    var value = x
    def modifyValue(newValue:Int):Unit = { value = newValue }
}

我的理解是,如果我们将其包装在如下所示的 State monad 中,它仍会修改原始内容,从而使包装它变得毫无意义。

case class State[S,+A](run: S => (A, S)) { // See footnote
    // map, flatmap, unit, utility functions
}

val sideEffect = new SideEffect(20)

println(sideEffect.value) // Prints "20"

val stateMonad = State[SideEffect,Int](state => { 
    state.modifyValue(10)
    (state.value,state)
})

stateMonad.run(sideEffect) // run the modification

println(sideEffect.value) // Prints "10" i.e. we have modified the original state

我能看到的唯一解决方案是复制 class 并修改它,但随着 SideEffect 的增长,这似乎在计算上很昂贵。另外,如果我们想包装像 Java class 这样没有实现 Cloneable 的东西,我们就不走运了。

val stateMonad = State[SideEffect,Int](state => { 
    val newState = SideEffect(state.value) // Easier if it was a case class but hypothetically if one was, say, working with a Java library, one would not have this luxury
    newState.modifyValue(10)
    (newState.value,newState)
})

stateMonad.run(sideEffect) // run the modification

println(sideEffect.value) // Prints "20", original state not modified

我是不是用错了State monad?如何包装一个有副作用的 class 而不必复制它,或者这是唯一的方法吗?

除了在某些包装器中隐藏变异外,您不能对可变对象做任何事情。因此,在测试中需要更多关注的程序范围将小得多。你的第一个样本就足够了。只有一瞬间。最好完全隐藏外部引用。相反 stateMonad.run(sideEffect) 使用类似 stateMonad.run(new SideEffect(20))

的东西
def initState: SideEffect = new SideEffect(20)
val (state, value) = stateMonad.run(initState)