Search code examples
scalastate-monad

Wrapping a class with side-effects


After reading chapter six of Functional Programming in Scala and trying to understand the State monad, I have a question regarding wrapping an side-effecting class.

Say I have a class that modifies itself in someway.

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

My understanding is that if we wrapped this in a State monad as below, it would still modify the original making the point of wrapping it moot.

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

The only solution to this that I can see is to make a copy of the class and modify that, but that seems computationally expensive as SideEffect grows. Plus if we wanted to wrap something like a Java class that doesn't implement Cloneable, we'd be out of luck.

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

Am I using the State monad wrong? How would one go about wrapping a side-effecting class without having to copy it or is this the only way?

  • The implementation for the State monad I'm using here is from the book, and might differ from Scalaz implementation

Solution

  • You can't do anything with mutable object except hiding mutation in some wrapper. So the scope of program that requires more attention in testing will be much more smaller. Your first sample is good enough. One moment only. It would be better to hide outer reference at all. Instead stateMonad.run(sideEffect) use something like stateMonad.run(new SideEffect(20)) or

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