Tagged Type 和 Play 编译错误 Json Format typeclass derivation

Compile error with Tagged Type and Play Json Format typeclass derivation

在下面的示例中,我想为我的 classes 的 ID 使用标记类型。我创建了一个实用特性来减少一些样板文件(tags/reads/writes 声明):

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]
}

编译器出现以下错误:

  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)

我无法解释我为什么会出现这种行为,错误消息并不明确。我正在为 FooIdBarId 导入 Format 以获得 Bar class.

的格式

问题是隐含的名称很重要。

下面是一个非常简单的例子:

object MyObject {
  implicit val i: Int = ???
}

import MyObject._

implicit val i: String = ???

// implicitly[Int] // doesn't compile
// implicitly[String] // doesn't compile

但是

object MyObject {
  implicit val i: Int = ???
}

import MyObject._

implicit val i1: String = ???

implicitly[Int] // compiles
implicitly[String] // compiles

如果您希望推导 Json.format[Bar] 起作用,范围内应该有隐式 Format[Bar.BarId.T]Format[Foo.FooId.T],即 Bar 字段的 Format 个实例。如果你只做 import

import Foo.FooId._
implicitly[Format[Foo.FooId.T]] // compiles

import BarId._
implicitly[Format[Bar.BarId.T]] // compiles

但是如果你同时导入两者,因为隐含的名称会发生​​冲突

import Foo.FooId._
import BarId._
// implicitly[Format[Foo.FooId.T]] // doesn't compiles
// implicitly[Format[Bar.BarId.T]] // doesn't compiles

例如,您可以将 trait Tag 移到 trait Opaque 之外并进行唯一导入。那么

implicitly[Format[Foo.FooId.T]]
implicitly[Format[Bar.BarId.T]]
Json.format[Bar]

将编译。

https://youtu.be/1h8xNBykZqM?t=681 我们在设计隐式函数时犯的一些错误,错误 #1