我的目标是为少数我无法修改其同伴的
Schema[T]
类实现Writes[T]
(来自scala-jsonschema)和
T
(来自play-json)。我的子目标是将它们紧密地定义在一起,以帮助它们在重构时保持同步。
为此,我创建了一个小辅助特征将它们配对在一起,并创建了一些隐式特征来隐式拉出
schema
和 writes
trait SchemaHelper[T] {
def schema: Schema[T]
def writes: Writes[T]
}
implicit def writesFromHelper[T](implicit c: SchemaHelper[T]): Writes[T] = c.writes
implicit def schemaFromHelper[T](implicit c: SchemaHelper[T]): Schema[T] = c.schema
我看到的问题是,如果我在当前隐式搜索范围中定义多个
SchemaHelper
(即使它们是不同的类型;这不是一个模糊的隐式问题),那么它们中的 none 都不起作用。
例如:
object MySchemas {
implicit val stringHelper: SchemaHelper[String] = new SchemaHelper[String] {
def schema = Schema.string
def writes = Writes.of[String]
}
implicit val intHelper: SchemaHelper[Int] = new SchemaHelper[Int] {
def schema = Schema.integer
def writes = Writes.of[Int]
}
implicit val fooHelper: SchemaHelper[Foo] = new SchemaHelper[Foo] {
// this example uses macro-generated values, but many of my
// actual classes will not, or will build on the macro-generated result
def schema = json.Json.schema[Foo]
def writes = play.api.libs.json.Json.writes[Foo]
}
// ...and so on, but with my actual classes
}
object Main extends App {
implicit def writesFromHelper[T](implicit c: SchemaHelper[T]): Writes[T] = c.writes
implicit def schemaFromHelper[T](implicit c: SchemaHelper[T]): Schema[T] = c.schema
import MySchemas._
val exampleSchema = implicitly[Schema[Foo]] // does not work
val exampleSchema2 = schemaFromHelper[Foo// works
}
在此示例中,
implicitly[Schema[Foo]]
未解析(尽管 IntelliJ 认为它可以解析,甚至在其隐式弹出窗口中指向 schemaFromHelper
)。
如果我注释掉
stringHelper
和 intHelper
,以便 fooHelper
是 MySchemas 中唯一隐式定义的 SchemaHelper
,implicitly[Schema[Foo]]
会再次起作用。这与我对......每个类型类的理解背道而驰。我应该能够在范围内拥有隐式的 SchemaHelper[A]
和 SchemaHelper[B]
而不会出现问题。
在我尝试调试时,我发现了
-Xlog-implicits
标志,它给了我......无用的输出:
[error] <redacted>\schemas.scala:17:29: implicit error;
[error] !I e: json.Schema[example.Foo]
[error] schemaFromHelper invalid because
[error] !I evidence$2: example.SchemaHelper[T]
[error] val exampleSchema = implicitly[Schema[Foo]]
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
此时我很困惑。对我来说这似乎是一个编译器错误,但我不确定。
事情似乎与
Schema[T]
具有协变性,而 Writes[T]
和 SchemaHelper[T]
是不变类型类。
确实,
trait Schema[+T]
trait Writes[+T]
trait SchemaHelper[+T] {
def schema: Schema[T]
def writes: Writes[T]
}
implicit def writesFromHelper[T](implicit c: SchemaHelper[T]): Writes[T] = c.writes
implicit def schemaFromHelper[T](implicit c: SchemaHelper[T]): Schema[T] = c.schema
case class Foo(id: Int, name: String)
implicit val intHelper: SchemaHelper[Int] = new SchemaHelper[Int] {
def schema: Schema[Int] = ???
def writes: Writes[Int] = ???
}
implicit val fooHelper: SchemaHelper[Foo] = new SchemaHelper[Foo] {
def schema: Schema[Foo] = ???
def writes: Writes[Foo] = ???
}
implicitly[SchemaHelper[Foo]]
implicitly[Writes[Foo]]
implicitly[Schema[Foo]]
https://scastie.scala-lang.org/DmytroMitin/uICRbhWvSUWrfgnZ3SHZvQ
在 2.13.12 中编译并且
trait Schema[T]
trait Writes[T]
trait SchemaHelper[T] {
def schema: Schema[T]
def writes: Writes[T]
}
...
https://scastie.scala-lang.org/DmytroMitin/uICRbhWvSUWrfgnZ3SHZvQ/1
编译但是
trait Schema[+T]
trait Writes[T]
trait SchemaHelper[T] {
def schema: Schema[T]
def writes: Writes[T]
}
...
https://scastie.scala-lang.org/DmytroMitin/uICRbhWvSUWrfgnZ3SHZvQ/2
没有。
这已在 Scala 3 中修复:https://scastie.scala-lang.org/DmytroMitin/uICRbhWvSUWrfgnZ3SHZvQ/3
解决方法是为
Schema[T]
引入“不变”类型别名
type Schema[T] = json.Schema[T] // NOT type Schema[+T] = json.Schema[T]
https://scastie.scala-lang.org/DmytroMitin/GBTHb10wT0iV9jK4dwg79A/3
(我在引号中写“不变”,因为别名类型
Schema[T]
仍然是协变的,implicitly[Schema[t1] <:< Schema[t2]]
代表t1 <: t2
:https://scastie.scala-lang.org/DmytroMitin/GBTHb10wT0iV9jK4dwg79A/8)