问题是 var 没有给你足够的控制权来做你想做的事。
作为对您问题的直接回答:您需要一个具有更新方法而不是字段的对象。像这样的东西:
class Box[T](var value:T) {
def apply = value
def update(newValue:T) { value = newValue }
override def toString = value.toString
}
然后你可以像这样解决你的问题:
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
但请注意,这确实不是非常惯用的 scala。您应该尝试使您的 someDangerousActionPotentiallyModifyingStep 成为返回新步骤的无副作用函数。
基本上是这样的:
try {
step = someDangerousFunctionOfStep(step)
} catch {
case _ => // we don't have to do anything because step is still the same
}
如果由于某种原因无法做到这一点,您可能需要研究Akka 代理,它们在概念上类似于上面的 Box,不同之处在于它们是线程安全的并且可以以事务方式使用。
以下是使用 akka 代理解决问题的方法:
首先你需要一个演员系统:
implicit val actorSystem = akka.actor.ActorSystem("test")
然后您可以定义一个包含步骤值的代理
val step = akka.agent.Agent(0)
现在您可以在事务中更新它:
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
当您拥有多个代理并以原子方式更新它们时,akka 代理的真正威力就来了。有关规范的“帐户转移”交易示例,请参阅 akka 代理文档。