I have a Scala trait for persistence and transaction management (which I will blog about in more detail later). The trait looks like:
trait DomainManager { def get[E](id: Long)(implicit m: ClassManifest[E]): Option[E] def find[E](namedQuery: String, params: Map[String, Any] = null): Option[E] //...more methods def withTransaction[R](f: (TransactionStatus) => R, readOnly: Boolean = false, propagationBehavior: PropagationBehavior = PropagationRequired): R }
Let’s take a look at withTransaction()
specifically. It is called like:
val result = domainManager.withTransaction { txStatus => // Access database here }
If your application is written in Scala or Java it is sometimes handy to have certain pieces of it written in Groovy, to be able to easily change a class and reload it without restarting the application. Your Groovy code will be able to call any of your Java classes, but what about your Scala classes? For example, what if we want to call withTransaction()
on DomainManager
? How do we deal with the f
parameter? And what about the default parameter values?
Groovy and Scala both have the concept of functions as first class objects. In Groovy they are called closures and they are implemented by the class groovy.lang.Closure
. In Scala they are called functions and they are implemented by the traits scala.Function0, scala.Function1, ... scala.FunctionN
, where N is the number of parameters to the function.
In withTransaction()
the type of f
is Function1[TransactionStatus, R]
. It is a function that takes one parameter of type TransactionStatus
and returns a generic R
.
Through Groovy magic closures can be coerced to arbitrary interfaces. For example, I wrapped the withTransaction()
method in Groovy like this:
def withTransaction(Closure closure) { domainManager.withTransaction(closure as Function1<TransactionStatus, Object>, false, PropagationRequired) }
Here the closure
parameter is coerced (using the Groovy as
coercion operator) to a Scala Function1[TransactionStatus, _]
which is the proper type after the generic parameter R
is erased.
You cannot use Scala’s default parameter values in Groovy or Java so the last two parameters (readOnly
and propagationBehavior
) need to be passed explicitly.
Now I can call my Groovy withTransaction()
with a closure like:
def result = withTransaction { txStatus -> // Access database here }
The same technique can be used to call Scala collection methods like List.map()
with Groovy closures.