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 :)
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.