Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whether to use context bound or implicit ev in Scala

Tags:

scala

According to the style guide - is there a rule of thumb what one should use for typeclasses in Scala - context bound or implicit ev notation?

These two examples do the same

Context bound has more concise function signature, but requires val evaluation with implicitly call:

def empty[T: Monoid, M[_] : Monad]: M[T] = {
    val M = implicitly[Monad[M]]
    val T = implicitly[Monoid[T]]
    M.point(T.zero)
}

The implicit ev approach automatically inserts typeclasses into function parameters but pollutes method signature:

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = {
  M.point(T.zero)
}

Most of the libraries I've checked (e.g. "com.typesafe.play" %% "play-json" % "2.6.2") use implicit ev

What are you using and why?

like image 481
Eugene Zhulkov Avatar asked Jan 30 '23 16:01

Eugene Zhulkov


1 Answers

This is very opinion-based, but one pratical reason for using an implicit parameter list directly is that you perform fewer implicit searches.

When you do

def empty[T: Monoid, M[_] : Monad]: M[T] = {
  val M = implicitly[Monad[M]]
  val T = implicitly[Monoid[T]]
  M.point(T.zero)
}

this gets desugared by the compiler into

def empty[T, M[_]](implicit ev1: Monoid[T], ev2: Monad[M]): M[T] = {
  val M = implicitly[Monad[M]]
  val T = implicitly[Monoid[T]]
  M.point(T.zero)
}

so now the implicitly method needs to do another implicit search to find ev1 and ev2 in scope.

It's very unlikely that this has a noticeable runtime overhead, but it may affect your compile time performance in some cases.

If instead you do

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] =
  M.point(T.zero)

you're directly accessing M and T from the first implicit search.

Also (and this is my personal opinion) I prefer the body to be shorter, at the price of some boilerplate in the signature.

Most libraries I know that make heavy use of implicit parameters use this style whenever they need to access the instance, so I guess I simply became more familiar with the notation.


Bonus, if you decide for the context bound anyway, it's usually a good idea to provide an apply method on the typeclass that searches for the implicit instance. This allows you to write

def empty[T: Monoid, M[_]: Monad]: M[T] = {
  Monad[M].point(Monoid[T].zero)
}

More info on this technique here: https://blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7

like image 105
Gabriele Petronella Avatar answered Feb 11 '23 14:02

Gabriele Petronella