如何在不使用它的情况下与依赖类型进行模式匹配?

How to pattern match with dependent type without using it?

这很难表达,请让我举个例子:

trait Cache

trait QueryLike {
  type Result
}

trait Query[A] extends QueryLike {
  type Result = A
  def exec: Result
}

trait CachedQuery[A] extends QueryLike {
  type Result = A
  def execWithCache(cache: Cache): Result
}

def exec(query: QueryLike)(implicit cache: Cache): query.Result = query match {
  case q: Query[query.Result] => q.exec
  case cq: CachedQuery[query.Result] => cq.execWithCache(cache)
}

由于模式匹配是在不同类型(QueryCachedQuery)上完成的,而不是依赖像 this question.

这样的泛型,因此编译和运行良好

但我仍然收到编译器警告,如:

Warning:(18, 12) abstract type Result in type pattern A$A4.this.Query[query.Result] is unchecked since it is eliminated by erasure case q: Query[query.Result] => q.exec

因为无论如何我都不直接处理依赖类型 query.Result(比如为不同的操作转换它),最好完全擦除它并消除警告。但不幸的是,使用通配符是有原因的:

...
case q: Query[_] => q.exec // type mismatch
case cq: CachedQuery[_] => cq.execWithCache(cache)
...

有没有更好的方法在不产生编译器警告的情况下执行此操作?

此错误并非特定于路径相关类型。如果您尝试匹配任何 Query[A],您将得到相同的错误,因为类型参数 A 在运行时被删除。在这种情况下,类型参数不可能是您要查找的类型以外的任何类型。由于 Query[A]QueryLike { type Result = A},因此它也应该是 Query[query.Result],尽管这种看待它的方式有点不寻常。如果您愿意,您可以使用@unchecked注释来抑制警告:

def exec(query: QueryLike)(implicit cache: Cache): query.Result = query match {
  case q: Query[query.Result @unchecked] => q.exec
  case cq: CachedQuery[query.Result @unchecked] => cq.execWithCache(cache)
}

虽然很难说这是否适用于您的实际用例,但您也可以重构代码以避免完全匹配,并通过多态性(可能)更优雅地处理它。由于最后一个 exec 需要一个隐含的 Cache 无论如何 ,允许每个 QueryLike 似乎没有什么坏处。您的 API 可以通过这种方式更加统一,并且您无需弄清楚要调用哪个方法。

trait Cache

trait QueryLike {
  type Result
  def exec(implicit cache: Cache): Result
}

trait Query[A] extends QueryLike {
  type Result = A
}

trait CachedQuery[A] extends QueryLike {
  type Result = A
}

def exec(query: QueryLike)(implicit cache: Cache): query.Result = query.exec

如果 Query[A] 需要 exec 而没有 Cache,您还可以提供带有 DummyImplicit 的重载以允许它在没有

的情况下工作。

def exec(implicit d: DummyImplicit): Result

实际上,问题似乎与路径相关类型非常具体:问题在于 q 和 cq 的类型。 q.Result 是与 query.Result 不兼容的类型,因为 Scala 类型检查器不知道 query 和 q 和 qc 必须是相同的引用。

因此,Query[query.Result] 实际上确实需要运行时类型检查。当我尝试删除类型参数并仅使用内部结果时,我注意到了这一点。然后模式匹配不再生成警告,但是 q.exec 的 return 类型将与 query.Result.

不兼容

不使用@unchecked 或asInstanceOf 等的一种解决方案是将Result 转换为QueryLike 的类型参数。通常,您不应该将抽象类型成员用于您想要 "out of a context" 自由传递的事物。因此,在继承层次结构的某处将类型成员转换为类型参数有点奇怪。

所以这工作正常,因为编译器知道,他不必检查类型参数:

trait QueryLike[A] {

}

trait Query[A] extends QueryLike[A] {
  def exec: A
}

trait CachedQuery[A] extends QueryLike[A] {
  def execWithCache(cache: Cache): A
}

def exec[A](query: QueryLike[A])(implicit cache: Cache): A = query match {
  case q: Query[A] => q.exec
  case cq: CachedQuery[A] => cq.execWithCache(cache)
}

另一种方法是将该方法添加到 CachedQuery 和 Query 的公共基本特征中。

trait QueryLike {
  type Result
}

trait Query[A] extends QueryLike with ExecWithCache {
  type Result = A
  def exec: Result
  override def execWithCache(implicit cache: Cache) = exec
}

trait CachedQuery[A] extends QueryLike with ExecWithCache {
  type Result = A
  def exec(cache: Cache): Result
  override def execWithCache(implicit cache: Cache) = exec(cache)
}

trait ExecWithCache extends QueryLike {
  def execWithCache(implicit cache: Cache): Result
}

要改变这一点,Scala 可能必须能够确定两个稳定访问器何时相同。