user2953119
user2953119

Reputation:

Understanding function in scala

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 implicits: 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

Answers (2)

Victor Moroz
Victor Moroz

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

laughedelic
laughedelic

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

Related Questions