I want to have a sort of "transaction" construct, on which I'm doing all of the changes and then decide if to commit or rollback at the end. My issue is that I don't know how to properly define / pass the implicit values without defining them manually from where the functions are called. How can this be accomplished?
class Foo {
var m = scala.collection.mutable.HashMap.empty[String, String]
case class Tx(mcopy: scala.collection.mutable.HashMap[String, String]) {
def commit = (m = mcopy)
def rollback = () // not copying mcopy will lose all changes made to it
}
def withTx(block: Foo => Unit): Unit = {
implicit val tx = new Tx(m.clone)
try {
block(this)
tx.commit
} catch {
case _: Throwable => tx.rollback
}
}
implicit val emptyTx = new Tx(m) // non-tx operations will be performed directly on 'm'
def add(k: String, v: String)(implicit t: Tx): Unit = (t.mcopy += k -> v)
}
val f = new Foo
f.add("k0", "v0") // error: no implicit t defined...
f.withTx { foo => foo.add("k1", "v1") } // errors as well on missing implicit
Without commenting on the wisdom of this (I think it depends), nothing prevents you from supplying default arguments on your implicit params. If you do this, a resolved implicit will take precedence, but if no implicit is found, the default argument will be used.
However, your
withTxfunction won't work no matter what, because the implicit you define is not in the scope from the function block. (You could not have referred totxfrom a function you define there.)To modify your example (giving transactions a label to make this clear):
Then...
But your withTx(...) function doesn't do what you want, and now, unhelpfully, it doesn't call attention to the fact it doesn't to what you want with an error. It just does the wrong thing. Instead of getting the implicit value that is not in scope, the operation in
blockgets the default argument, which is the opposite of what you intend.Update:
To get the kind of
withTxmethod you want, you might try:Users would need to mark the supplied transaction as
implicitin their blocks. It would be something like this:So that "worked". But actually, since you put an automatic commit in after the block completes, unless it completes with an exception, my call to tx.commit was unnecessary.
I don't think that's a great choice. Watch this:
The
add(...)completed despite my explicit call torollback! That's becauserollbackis just a no-op and an automatic commit follows.To actually see the rollback, you need to throw an
Exception:Now, finally, we can see a call to
add(...)that was reverted.