scala中的配对类型参数和参数子类型参数

Pairing type argument and argument sub-type argument in scala

我发现实现可以巧妙地解决我的问题时遇到了麻烦:我想对一些 "converter" 的不同输出类型进行多个实现,但所有实现都共享一些架构定义。

单靠这个词很难解释,所以这里有一个示例代码:

sealed trait Schema[+A]

object Schema{
  // Schema definition
  object IntSchema extends Schema[Int]
  // ...
  case class SeqSchema[B](bSchema: Schema[B]) extends Schema[Seq[B]]
  // ...
  case class MappedSchema[A,B](aToB: A => B, bSchema: Schema[B]) extends Schema[A]
  // ...

  // base implementations of conversion to "Dst"
  trait Dst // some output type, used below
  def convertIntToDst(i: Int): Dst = ???
  def convertSeqToDst[A](s: Seq[A]): Dst = ???
  // ...


  // *** here is what I want to do: ***
  // combine base conversion using schema
  def convertToDst[A](a: A, schema: Schema[A]): Dst = schema match {

    case IntSchema =>
      convertIntToDst(a.asInstanceOf[Int]) // (1) asInstanceOf :(

    case s: SeqSchema[_] =>
      convertSeqToDst(a.asInstanceOf[Seq[_]].map{ ai => // (2)
        convertToDst(ai, s.bSchema) // (3)
      })

    case s: MappedSchema[_,_] =>
      convertToDst(s.aToB(a), s.bSchema) // (4) fails to compile!!
  }

  // Would want to implement some conversion to other types, still using schema 
}

如您所见,我无法将 converterToDst 的第一个参数的 A 类型 "pairing" 转换为 schema 参数的类型参数。但是,我认为从方法定义来看,两者可以一起使用应该是安全的。 我得到的错误是

[...] type mismatch;
[error]  found   : a.type (with underlying type A)
[error]  required: _
    convertToDst(s.aToB(a), s.bSchema)

所以我有两个问题:

  1. 如何使案例 (4) 编译通过?
  2. 不是最重要的,但是有没有办法不在标记为 (1) 和 (2) 的行上投射 a

我认为对于第一个问题,但仍然使用 asInstanceOf,如果我可以在匹配案例中命名类型参数,例如 case s: MappedSchema[A,B] =>,我可以解决一些问题。但这看起来不可能(由于类型擦除,这可能是个坏主意)

另外注意,发现这个issue后,发现case(3)编译的时候很奇怪:(来自IntelliJ)ai is Any and c.bSchema is Schema[B].

我认为使用 typeclasses.

会更加类型安全和可扩展

这是使用这种方法重写的代码,如果您在将其适应实际用例时遇到任何问题,请告诉我。

trait Dst

sealed trait Schema[A] {
  def convertToDst(input: A): Dst

  final def contraMap[B](bToA: B => A): Schema[B] = new Schema[B] {
    override final def convertToDst(input: B): Dst =
      this.convertToDst(input = bToA(input))
  }
}

object Schema {
  final implicit val IntSchema: Schema[Int] =
    new Schema[Int] {
      override final def convertToDst(input: Int): Dst =
        ???
    }

  implicit def listSchema[A](implicit aSchema: Schema[A]): Schema[List[A]] =
    new Schema[List[A]] {
      override final def convertToDst(input: List[A]): Dst =
        ???
    }

  def convertToDst[A](a: A)(implicit schema: Schema[A]): Dst =
    schema.convertToDst(input = a)
}

您可以在模式中命名类型参数:

case s: SeqSchema[a1] =>
  convertSeqToDst(a.asInstanceOf[Seq[a1]].map{ ai => // (2)
    convertToDst(ai, s.bSchema) // (3)
  })

case s: MappedSchema[a1,b1] =>
  convertToDst(s.aToB(a.asInstanceOf[a1]), s.bSchema) // (4) 

I found that it is strange that the case (3) compiles: (from intellij) ai is Any and c.bSchema is Schema[B].

因为您将 Schema 声明为协变 ([+A]),所以 Schema[B] 也是 Schema[Any],无论 B 是什么。

您可以通过将第一个类型参数固定为 A:

来帮助编译器推断类型
 case s: MappedSchema[A, _] =>
      convertToDst(s.aToB(a), s.bSchema)

适用于 Scala 2.13 和 2.12。