A couple of days ago (translation for the masses: like a month ago) I noticed Viktor Klang was tweeting about removing the annoying implicit evidences from methods. And some things I read seemed so elegant to me that I was forced to share some related ideas with all of you that don’t follow him at Twitter (@viktorklang).
Setting some context
Imagine the typical polymorphic method where we need an execution context for evaluating some Future:
import scala.concurrent.{ExecutionContext, Future} def myMethod[T] (element: T) (implicit ev: ExecutionContext): Future[Boolean] = ???
You could say it’s as typical as disgusting, having to repeat the same exact words in the following 10 method definitions: (implicit ev: ExecutionContext)
.
Playing with type alias
The happy idea that is being proposed is to define a type alias like the following one:
type EC[_] = ExecutionContext
This way, by adding some syntax sugar, we would re-define the method signature:
def myMethod[T:EC](element: T): Future[Boolean] = ??? myMethod("hi")
Beautiful, isn’t it?
Some other possibilities
Non-polymorphic methods
In case our method isn’t parameterized, we would have to add some boilerplate (by adding a wildcard for the type that parameterizes the method). In essence, it should be working the same principle:
def myMethod[_:EC](element: Int): Future[Boolean] = ??? myMethod(2)
Multiple implicit contexts
The not-so-crazy case in which we needed several implicit parameters of different natures, we would have to define as many type alias as different type parameters we required:
type EC[_] = ExecutionContext type MongoDB[_] = MongoDBDatabase def myMethod[_:EC:MongoDB](element: Int): Future[Boolean] = ???
But what if …?
Multiple implicit parameters with same type
In case we have several implicit parameters that share the same type,
def myMethod (element: Int) (implicit ev1: ExecutionContext, ev2: ExecutionContext): Future[Boolean] = ???
it turns out that …
Well, by definition that’s impossible given that it would incur in some ambiguity issue when resolving implicits. It’s true that Scala allows having these kind of signatures, but we could only invoke them by making explicit the arguments contained in the second parameter group.:
myMethod(2)(ec1,ec2)
which is kind of…
Type-constructor implicit contexts
When we have implicit parameters that are also type constructors like List[T], Future[T], Option[T]
…
…well, it actually depends.
Case 1
If the type that parameterizes the method and the one that parameterizes the evidence are not related, there’s no big deal: we define another type alias and move on:
type EC[_] = ExecutionContext type MongoDB[_] = MongoDBDatabase type IntOpt[_] = Option[Int] type StrList[_] = List[String] def myMethod[_:EC:MongoDB:IntOpt:StrList]( element: Int): Future[Boolean] = ???
Which would be equivalent to:
def myMethod( element: Int)( implicit ev1: ExecutionContext, ev2: MongoDBDatabase, ev3: Option[Int], ev4: List[String]): Future[Boolean] = ???
Case 2
If the type that parameterizes the method and the one that parameterizes the evidence have to match …
Well, it’s not possible. The syntax sugar we’re using here implies that both types have to match. Maybe it was too pretty for our bodies 🙂
See you in the next post. Peace out!
One way to deal with (implicit ev1: ExecutionContext, ev2: ExecutionContext) is type tagging. It’s also a nice idea even if you have a single ExecutionContext, just to avoid passing wrong implicit EC by mistake. Tagging is available as a feature of Shapeless. There’s also a micro-library for this: https://github.com/softwaremill/scala-common#tagging
Me gustaMe gusta
Nice feature!
Me gustaMe gusta