存在多个实例时隐式解析失败,尽管具有不同的类型

问题描述 投票:0回答:1

我的目标是为少数我无法修改其同伴的

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]
而不会出现问题。

Scastie 示例

在我尝试调试时,我发现了

-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

此时我很困惑。对我来说这似乎是一个编译器错误,但我不确定。

scala implicit
1个回答
0
投票

事情似乎与

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

© www.soinside.com 2019 - 2024. All rights reserved.