尽管有证据,但自类型下的类型参数不符合上限



我有一个特性,它带有一个带有类型参数的自类型注释。此特征来自库,无法修改。我想把这个特性传递给一个函数,该函数需要类型参数的上界。例如,我有以下代码片段:

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]()

这样做的问题是,虽然类型参数UT都是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所做的基本相同。

最新更新