Search code examples
scalaakka

Immutable POD class with mutable partner


I have found myself running into error-prone code; the pattern is this:

final class StateVars(val x:Int, val y:Int)

final class StateVarsMutable(var x:Int, var y:Int) {
  // req. case classes & >1 element w/ same ordering
  // - can also do similar with shapeless
  // def snapshot():StateVars = StateVars.tupled( 
  //  StateVarsMutable.unapply(this).get )
  def snapshot() = new StateVars(x, y) // generic
}

In the above an instance StateVarsMutable might be contained within an Actor, but the actor may occasionally send a snapshot of its state with StateVars. It looks like kailuowang's henkan could be used. Without compromising run-time performance, is there a best or common approach to this situation?


Solution

  • Mutability of Immutable Classes

    The pattern described in the question is unnecessary.

    The case class in scala solves the problem that the question is trying to address by using persistent data structure patterns.

    Modifying the question's class declaration to include "case":

    final case class StateVars(x: Int, y: Int)
    

    It is now possible to "mutate" this state by returning new objects that represent the new state:

    val initialState = StateVars(0,0)
    
    //Increment x while keeping y the same
    val incrementX : StateVars = initialState copy (x = initialState.x + 1)
    
    //Increment y while keeping x the same
    val incrementY : StateVars = initialState copy (y = initialState.y + 1)
    

    If you need to continuously maintain a "snapshot" then use a var:

    var snapshot : StateVars = StateVars(0,0)
    
    snapshot = snapshot copy (x = snapshot.x + 1, y = snapshot.y +1)
    

    Within Actors

    It is possible to write an Actor that maintains a "snapshot" using the become functionality to allow for changing values:

    object StateVarsRequest
    
    class StateVarsActor(initialStateVars : StateVars) extends Actor {
    
      private def update(currentSnapshot: StateVars) : Receive = {
        case stateVars : StateVars => context become update(stateVars)
        case _ : StateVarsRequest  => sender ! currentSnapshot
      }
    
      override def receive : Receive = update(initialStateVars)
    }
    

    The snapshot can be thought of as a pointer to an immutable class. When a requester receives the snapshot in a message they are not getting the mutable pointer, but rather the immutable data.

    So if the snapshot changes after a requester has received their data then the requester's data does not change.