scala 自身类型下的类型参数不符合上界,尽管有证据

yiytaume  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(173)

我有一个带有自类型注解的特征,该注解有一个类型参数。此特性来自库,不能修改。我想将此特征传递给一个函数,该函数需要类型参数的上限。例如,我有以下代码片段:

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函数?

hc2pp10m

hc2pp10m1#

为什么不起作用?

您的问题与“广义类型约束”无关。您可以删除它,但仍然会收到相同的错误。广义类型约束用于约束该方法可以接收的参数类型。
(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上运行。

9w11ddsr

9w11ddsr2#

根据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所做的是相同的。

相关问题