Home > Enterprise >  What is the usage of an `implicit def` without arguments in Scala?
What is the usage of an `implicit def` without arguments in Scala?

Time:08-17

I saw this piece of code and I don't know how it will be used. I learned that implicit def is used for implicit conversion and it comes with an argument so that it can convert its argument to another type. Please help to explain the usage of the implicit below, any example is great. Thank you in advance!

  implicit def eitherWrites[A: Writes, B: Writes] = Writes[Either[A, B]]({
    case Left(a) => implicitly[Writes[A]].writes(a)
    case Right(b) => implicitly[Writes[B]].writes(b)
  })

CodePudding user response:

The syntax A: Writes in the type parameter of eitherWrites is called a context bound.

def foo[T: C]

is syntactic sugar for

def foo[T](implicit evidence: C[T])

In other words: eitherWrites does have not one but two (implicit) parameters, since it is syntactic sugar for

implicit def eitherWrites[A, B](
  implicit evidence`1: Writes[A], 
           evidence`2: Writes[B]
) = Writes[Either[A, B]]({
  case Left(a) => implicitly[Writes[A]].writes(a)
  case Right(b) => implicitly[Writes[B]].writes(b)
})

CodePudding user response:

I can show you a different example to understand how implicit def is used in your case:

  // a Haskell-like type class that provides textual representation
  trait Show[T] {
    def show(x: T): String
  }

  object Show {

    def show[T: Show](x: T): String = implicitly[Show[T]].show(x)

    implicit val showString: Show[String] = identity(_)
    implicit val showInt: Show[Int]       = _.toString

    implicit def showTup[T: Show, U: Show]: Show[(T, U)] =
      tup => s"(${Show.show[T](tup._1)}, ${Show.show[U](tup._2)})"
  }

The implicit def is called a type class derivation or implicit derivation. Basically it gives you the ability to create a new type class based on current existing implicit parameters and implicit conversions that describes how to compose them together automatically.

The next 3 lines are equivalent:

  println(Show.show(123, "abc"))                  // (abc, 123)
  println(Show.show((123, "abc")))                // (abc, 123)
  println(Show.show(123 -> "abc"))                // (abc, 123)

Note the parameter here is a tuple of 2 elements, since show takes one argument. In this case, the compiler inserts an implicit conversion with 2 implicit parameters. The desugared code is:

  println(Show.show(123, "abc")(showTup(showInt, showString)))

You can also do:

println(Show.show((123, "abc"), (123, "abc")))  // ((123, abc), (123, abc))

This is a bit trickier to figure out how the compiler translates it, but the general rule remains: implicit parameters are inserted after the normal parameters, in a separate argument list. The desugared code is:

  println(
    Show.show((123, "abc"), (123, "abc"))(
      showTup(showTup(showInt, showString), showTup(showInt, showString))
    )
  )

If you can't figure it out on your own, run your code with the -Xprint:typer option, and the scalac compiler will show you what your code looks like after all implicit conversions have been added by the type checker.

This looks odd at first, but once you figure it out, it's actually quite limited because it only works with tuples of 2 elements. So for example, if you want a tuple of 3 elements, you'd have to write another implicit def that takes 3 context bounds and so on. Your example exhibits similar behavior.

  • Related