In the following example, I want to use tagged types for the ids of my classes. I've created an utility trait to reduce some boilerplate (of tags/reads/writes declaration):
import java.util.UUID
import play.api.libs.json.{Format, Json, Reads, Writes}
trait Opaque[A] {
protected type Tagged[U] = { type Tag = U }
type @@[U, T] = U with Tagged[T]
trait Tag
def tag(a: A): A @@ Tag = a.asInstanceOf[A @@ Tag]
def untag(a: A @@ Tag): A = a
implicit def reads(implicit r: Reads[A]): Reads[A @@ Tag] =
r.map(tag)
implicit def writes(implicit w: Writes[A]): Writes[A @@ Tag] =
w.contramap(untag)
implicit def format(implicit r: Reads[A], w: Writes[A]): Format[A @@ Tag] =
Format(reads(r), writes(w))
}
final case class Foo(id: Foo.FooId.T, f1: Boolean)
object Foo {
object FooId extends Opaque[UUID] {
type T = UUID @@ Tag
}
import FooId._
implicit val fmt: Format[Foo] = Json.format[Foo]
}
final case class Bar(id: Bar.BarId.T, fooId: Foo.FooId.T, b1: String)
object Bar {
object BarId extends Opaque[UUID] {
type T = UUID @@ Tag
}
import Foo.FooId._
import BarId._
implicit val format: Format[Bar] = Json.format[Bar]
}
I have the following error from the compiler:
implicit val format: Format[Bar] = Json.format[Bar]
^
<pastie>:43: error: No instance of play.api.libs.json.Format is available for Opaque.<refinement>, Opaque.<refinement> in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
I'm not able to explain why I'm having this behaviour, the error message is not explicit. I'm importing the Format
for FooId
and BarId
needed for deriving a format for Bar
class.
The thing is that names of implicits are significant.
Very simple example of that is following:
object MyObject {
implicit val i: Int = ???
}
import MyObject._
implicit val i: String = ???
// implicitly[Int] // doesn't compile
// implicitly[String] // doesn't compile
but
object MyObject {
implicit val i: Int = ???
}
import MyObject._
implicit val i1: String = ???
implicitly[Int] // compiles
implicitly[String] // compiles
If you want derivation Json.format[Bar]
to work, there should be implicits Format[Bar.BarId.T]
, Format[Foo.FooId.T]
in scope i.e. Format
instances for fields of Bar
. If you make the only import
import Foo.FooId._
implicitly[Format[Foo.FooId.T]] // compiles
and
import BarId._
implicitly[Format[Bar.BarId.T]] // compiles
but if you import both, since names of implicits collide
import Foo.FooId._
import BarId._
// implicitly[Format[Foo.FooId.T]] // doesn't compiles
// implicitly[Format[Bar.BarId.T]] // doesn't compiles
For example you can move trait Tag
outside trait Opaque
and make the only import. Then
implicitly[Format[Foo.FooId.T]]
implicitly[Format[Bar.BarId.T]]
Json.format[Bar]
will compile.
https://youtu.be/1h8xNBykZqM?t=681 Some Mistakes We Made When Designing Implicits, Mistake #1