带有标记类型和播放 Json 格式类型类派生的编译错误

Posted

技术标签:

【中文标题】带有标记类型和播放 Json 格式类型类派生的编译错误【英文标题】:Compile error with Tagged Type and Play Json Format typeclass derivation 【发布时间】:2021-09-24 19:20:16 【问题描述】:

在下面的示例中,我想为我的类的 id 使用标记类型。我创建了一个实用程序特征来减少一些样板(标签/读取/写入声明):

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 类的格式。

【问题讨论】:

【参考方案1】:

事情是隐含的名字很重要。

一个非常简单的例子如下:

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],即Format 字段的Bar 实例。如果您进行唯一的导入

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

NullPointerException on implicit resolution

【讨论】:

谢谢你的回答,我明白问题出在哪里了。我会接受的。我在理解这部分时遇到了问题:“例如,您可以将 trait Tag 移到 trait Opaque 之外并进行唯一的导入。”。请问你能详细说明吗?可以肯定的是,我正在寻找的是为每个 Id 使用不同的标签,通过将 Tag 移到外面,我将不得不复制特征。 @stankoua 哦,我明白你为什么想要Tag inner。

以上是关于带有标记类型和播放 Json 格式类型类派生的编译错误的主要内容,如果未能解决你的问题,请参考以下文章

覆盖派生模板类的返回类型

关于使用jq 处理json格式的简单笔记

swagger参数是json类型的怎么标记哪些是必须的

C++将派生类赋值给基类(向上转型)

派生类型的自动静态调用

C++ 静态多态性 (CRTP) 和使用派生类的 typedef