Reputation:
The question is strictly about Scala syntax, although it contains some code from akka (as an example).
I'm pretty new to Scala. Digging into source code of akka I came up with the following pretty strange method:
def transform[C]
(f: ExecutionContext ⇒ Materializer ⇒ Future[B] ⇒ Future[C]): Unmarshaller[A, C] =
Unmarshaller.withMaterializer { implicit ec ⇒ implicit mat ⇒ a ⇒ f(ec)(mat)(this(a)) }
where Unmarshaller.withMaterializer
defined as
def withMaterializer[A, B](f: ExecutionContext ⇒ Materializer => A ⇒ Future[B]): Unmarshaller[A, B]
What's going on here? What is the scary function f: ExecutionContext => Materializer => Future[B] => Future[C]
. And what seemed more strange to me was the sequence of implicit
s: implicit ec => implicit mat => a => f(ec)(mat)(this(a))
although withMaterializer
does not have implicit parameters at all.
What does implicit mean in such sequences?
Upvotes: 6
Views: 104
Reputation: 9225
f: ExecutionContext => Materializer => Future[B] => Future[C]
is nothing more than a curried function, so you call it like f(ec)(mat)(this(a))
with multiple parameter lists (well, technically parameters lists don't belong to the same function unlike def f(...)(...)
, but those are details). In other words f
can be written as:
f: ExecutionContext => { Materializer => { Future[B] => Future[C] } }`
(function which returns a function, which returns yet another function)
Now if you look at f(ec)(mat)(this(a))
there is a call this(a)
, which is defined just above transform
:
def apply(value: A)(implicit ec: ExecutionContext, materializer: Materializer): Future[B]
(this(a)
is simply a call to this.apply(a)
). Now apply
has two implicit parameters, namely ec: ExecutionContext
and materializer:Materializer
, so to call it like this(a)
you need two implicit values. Which is exactly what definition implicit ec ⇒ implicit mat ⇒ a ⇒ f(ec)(mat)(this(a))
means. It declares ec
and mat
as implicits for all nested function bodies so this(a)
can pick them up. Another possibility would be to write:
ec ⇒ mat ⇒ a ⇒ f(ec)(mat)(this(a)(ec, mat))
Upvotes: 2
Reputation: 6460
It's a lambda with currying and implicit parameters (which are supposed to be in scope of the declaration).
The "scary" function type syntax is currying: a function of one argument which takes ExecutionContext
and returns another function of one argument that takes Materializer
and returns another function, ... etc. Another thing is implicit arguments.
Here's a simpler example of a similar construction:
implicit val implicitInt: Int = 5
implicit val implicitString: String = "0"
val f: Int => String => String = {
implicit a => {
implicit b => {
a.toString + b
}
}
}
Here f
is a curried function that takes Int
and returns a function that takes String
and returns String
. The general syntax for a function value declaration is val f = { argument => ... }
, so if you make this argument implicit, it means that there has to be an instance of this type in the scope which will work as default value. You can still apply f
to some arguments: f(1)("")
, because it's still a function.
You could rewrite the code you're asking about much more verbosely by defining nested functions for every step:
def transform[C](f: ExecutionContext ⇒ Materializer ⇒ Future[B] ⇒ Future[C]): Unmarshaller[A, C] = {
def getExecutionContext(implicit ec: ExecutionContext): Materializer => (A => Future[B]) = {
def getMaterializer(implicit mat: Materializer): A => Future[B] = {
def applyF(a: A): Future[B] = f(ec)(mat)(this(a))
applyF // : A => Future[B]
}
getMaterializer // : Materializer => (A => Future[B])
}
Unmarshaller.withMaterializer(
getExecutionContext // : ExecutionContext => (Materializer => (A => Future[B]))
)
}
Upvotes: 0