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.