- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
"Functional" JPA transaction composition
Sun, 2011-10-02, 16:05
Hi,
as a toy project to learn a bit more about custom usage of
for-comprehensions, I started looking into JPA transactions. (I don't
think this has any real-world value - as soon as it gets to types with
collection members JPA probably will hit the ceiling. This just looked
like a nice domain for learning purposes.)
I naively started along the path Tony Morris outlined in a post about
"Dependency Injection" in the debate list[1].
case class Transactional[V](fun: EntityManager => V)
extends (EntityManager => V) {
def apply(em: EntityManager) = fun(em)
def map[R](map: V => R) = Transactional(map compose fun)
def flatMap[R](map: V => Transactional[R]) =
Transactional((em: EntityManager) => map(fun(em)).fun(em))
}
object Transactional {
implicit def toTransactional[V](fun: EntityManager => V) =
Transactional(fun)
implicit def toPlainTransactional[V](v: V) = Transactional(em => v)
def trans[V](fun: EntityManager => V) =
Transactional((em: EntityManager) => {
em.getTransaction.begin()
try {
val res = fun(em)
em.getTransaction.commit()
res
}
catch {
case exc => {
em.getTransaction.rollback()
throw exc
}
}
})
}
In a next step, I wanted to ensure that Transactionals already wrapped
by a trans() invocation cannot be wrapped again (since there's no nested
transactions). And that's where I hit the wall. I naively tried to apply
"phantom types" starting like this:
sealed trait TransState
class TransFree extends TransState
class TransClosed extends TransState
case class Transactional[S <: TransState, V](fun: EntityManager => V) {
def exec(em: EntityManager) = fun(em)
def map[R](map: V => R) = Transactional[S, R](map compose fun)
def flatMap[S <: TransState, R](map: V => Transactional[S, R]) =
Transactional[S, R]((em: EntityManager) => map(fun(em)).fun(em))
}
object Transactional {
implicit def toTransactional[V](fun: EntityManager => V) =
Transactional[TransFree, V](fun)
implicit def toPlainTransactional[V](v: () => V) =
Transactional[TransFree, V](em => v())
implicit def toUnitTransactional(v: => Unit) =
Transactional[TransFree, Unit](em => v)
def trans[V](t: Transactional[TransFree, V]) =
Transactional[TransClosed, V]( em =>
// ...as above
)
}
...but of course this is wrong for the flatMap result state marker, and
I couldn't find any way to issue the "right" state depending on the
state of both "this" transactional and the one created by the map function.
(As you can see, I've given up on making Transactional a function
itself, since this would make it applicable to the implicit conversions
as well.)
My current attempt looks like this:
sealed trait Transactional[V] {
val fun: (EntityManager => V)
def exec(em: EntityManager) = fun(em)
def map[R](map: V => R): Transactional[R]
}
case class FreeTransactional[V](override val fun: EntityManager => V)
extends Transactional[V] {
override def map[R](map: V => R) =
FreeTransactional[R](map compose fun)
def flatMap[R](map: V => FreeTransactional[R]) =
FreeTransactional[R]((em: EntityManager) => map(fun(em)).fun(em))
def flatMap[R](map: V => ClosedTransactional[R]) =
ClosedTransactional[R]((em: EntityManager) => map(fun(em)).fun(em))
}
case class ClosedTransactional[V](override val fun: EntityManager => V)
extends Transactional[V] {
override def map[R](map: V => R) =
ClosedTransactional[R](map compose fun)
def flatMap[R](map: V => Transactional[R]) =
ClosedTransactional[R]((em: EntityManager) => map(fun(em)).fun(em))
}
object Transactional {
implicit def toTransactional[V](fun: EntityManager => V) =
FreeTransactional[V](fun)
implicit def toPlainTransactional[V](v: () => V) =
FreeTransactional[V](em => v())
implicit def toUnitTransactional(v: => Unit) =
FreeTransactional[Unit](em => v)
def trans[V](t: FreeTransactional[V]) =
ClosedTransactional[V]( em =>
// ...as above
)
}
This kind of seems to work - well, probably it doesn't and I've just
been missing the breaking case so far. But even if it does, it looks
awkward to me, and I guess there must be a cleaner solution.
(BTW, one thing that confuses me about the application of this
"solution": when used with for comprehensions, it seems to require me to
specify the type for unused flatMap application results, i.e. I need to
write "(_: Unit) <- {};" instead of "_ <- {};" - using explicit
map/flatMap chaining this doesn't seem to be the case...)
Any input is appreciated, be it suggestions for a better implementation
of this approach or a completely different design.
When it comes to the underlying concepts, please speak slowly for the
time being - my math is rusty in general and I've only started to get
accustomed to category theory concepts. (I've just started reading
"Conceptual Mathematics" - suggestions for books/online resources that
are either "better" or closer to the "Scala world" are welcome, too.)
Thanks in advance and best regards,
Patrick
[1] http://permalink.gmane.org/gmane.comp.lang.scala.debate/7004