Search code examples
scalagenericspass-by-referenceimplicittry-finally

generic variable rememberer without pass by reference


I have the following snippet of code:

val oldStep = step
try {
  someDangerousActionPotentiallyModifyingStep()
} finally {
  step = oldStep
}

Is it possible to write a generic helper method to abstract away the pattern? Then I could use the helper method like this:

remember(step) {
  someDangerousActionPotentiallyModifyingStep()
}

Of course the following approach does not work, because Scala does not support pass by reference:

def remember[T](x: T)(action: => Unit) {
  val previousValue = x
  try {
    action
  } finally {
    x = previousValue
  }
}

I solved the problem by changing the step data member into an implicit parameter. That way, I never have to restore the old value of step, because its value never changes inside the same scope :)


Solution

  • The problem is that a var does not give you enough control to do what you want to do.

    As a direct answer to your question: you need an object with an update method instead of a field. Something like this:

    class Box[T](var value:T) { 
      def apply = value
      def update(newValue:T) { value = newValue }
      override def toString = value.toString 
    } 
    

    Then you can solve your problem like this:

    def remember[T](box:Box[T])(action: =>Unit) { 
      val prev = box.apply
      try { 
        action 
      } catch { 
        case _ => box() = prev 
      } 
    }
    
    val step = new Box(0)
    
    // working update
    remember(step) { step() = 4 }
    
    // step is now 4
    
    // aborted update
    remember(step) { step() = 5; throw new Exception }
    
    // step is still 4
    

    But note that this is really not very idiomatic scala. You should try to make your someDangerousActionPotentiallyModifyingStep a side-effect-free function that returns the new step.

    Basically like this:

    try {    
      step = someDangerousFunctionOfStep(step)
    } catch {
      case _ => // we don't have to do anything because step is still the same
    }
    

    If that is not possible for some reason, you might want to investigate Akka agents, which are conceptually similar to the Box above, except with the difference that they are thread-safe and can be used in a transactional way.

    Here is how you would use akka agents to solve the problem:

    First you need an actor system:

    implicit val actorSystem = akka.actor.ActorSystem("test")
    

    Then you can define an agent containing the step value

    val step = akka.agent.Agent(0)
    

    Now you can update it in a transaction:

    import scala.concurrent.stm._    
    
    atomic { txn => step() = 4 } 
    // step.get will now return 4
    
    atomic { txn => step() = 5; throw new Exception }
    // step.get will still return 5. You will have to catch the exception if you don't want
    // it to propagate outward
    

    The real power of akka agents comes when you have multiple agents and update them atomically. See the akka agents docs for the canonical "account transfer" transaction example.