我有一个特性,它带有一个带有类型参数的自类型注释。此特征来自库,无法修改。我想把这个特性传递给一个函数,该函数需要类型参数的上界。例如,我有以下代码片段:
sealed trait Job[K] { self =>
type T
}
case class Encoder[T <: Product]()
def encoder(job: Job[_])(implicit ev: job.T <:< Product): Encoder[job.T] =
new Encoder[job.T]()
这将返回一个错误Type argument job.T does not conform to upper bound Product
和一个从未使用过ev
的警告。我应该如何设计encoder
功能?
为什么它不起作用
您的问题与广义类型约束无关。您可以删除它,但仍然会得到相同的错误。广义类型约束用于约束方法可以接收的参数的类型。
(implicit ev: job.T <:< Product)
在作用域中提供了仅当job.T <: Product
匹配的证据,只允许调用具有Job
参数的方法,其中job.T <: Product
。这就是它的目的。
您的问题是因为Encoder
类具有其类型参数T <: Product
。广义类型约束不会像您预期的那样将类型job.T
本身视为Product
的子类型。证据只适用于值参数,而不适用于类型本身,因为这就是隐式转换的工作方式。
例如,假设job.T
类型的值x
可以作为参数传递给方法:
def encoder(job: Job[_])(x: job.T)(implicit ev: job.T <:< Product): Unit = {
val y: Product = x // expands to: ev.apply(x)
val z: Encoder[Product] = new Encoder[job.T] // does not compile
}
第一行编译是因为x
被扩展为ev.apply(x)
,但第二行不能被扩展,无论Encoder
是否是协变。
第一个解决方法
你可以做的一个变通方法是:
def encoder[U <: Product](job: Job[_])(implicit ev: job.T <:< Product): Encoder[U] =
new Encoder[U]()
这样做的问题是,虽然类型参数U
和T
都是Product
的子类型,但此定义并没有说明它们之间的关系,编译器(甚至Intellij(也不会推断出正确的结果类型,除非您明确指定它。例如:
val myjob = new Job[Int] {
type T = (Int, Int)
}
val myencoder: Encoder[Nothing] = encoder(myjob) // infers type Nothing
val myencoder2: Encoder[(Int, Int)] = encoder[(Int, Int)](myjob) // fix
但是,如果我们已经有了U <: Product
,为什么要使用job.T <:< Product
呢。相反,我们可以使用=:=
证据来确保它们的类型是相等的。
def encoder[U <: Product](job: Job[_])(implicit ev: job.T =:= U): Encoder[U] =
new Encoder[U]()
现在将正确地推断出结果类型。
第二个解决方法
一个较短的解决方法是使用结构类型:
def encoder(job: Job[_] { type T <: Product }): Encoder[job.T] =
new Encoder[job.T]()
这不仅更干净(不需要广义类型约束(,而且避免了早期的问题。
这两个版本都适用于Scala 2.13.8。
扩展Alin的答案,您也可以使用类型别名来表示相同的东西,如下所示:
type JobProduct[K, P <: Product] = Job[K] { type T = P }
// Here I personally prefer to use a type parameter rather than an existential
// since I have had troubles with those, but if you don't find issues you may just use
// JobProdut[_, P] instead and remove the K type parameter.
def encoder[K, P <: Product](job: JobProduct[K, P]): Encoder[P] =
new Encoder[P]()
这种方法可能对新来者更具可读性,并允许重用;然而,这与Alin所做的基本相同。