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

时间:2016-02-08 04:14:24

标签: scala pattern-matching type-erasure path-dependent-type

这很难说,所以请让我举个例子:

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等泛型。

但是我仍然得到编译器警告:

  

警告:(18,12)抽象类型类型模式中的结果A $ A4.this.Query [query.Result]未被选中,因为它被擦除消除     case q:查询[query.Result] => q.exec

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

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

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

2 个答案:

答案 0 :(得分:2)

此错误并非特定于路径相关类型。如果您尝试匹配任何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 的情况下工作。

NSMangedObject

答案 1 :(得分:0)

实际上,问题似乎非常特定于路径依赖类型:问题是q和cq的类型。 q.Result是一个不兼容的查询类型。结果因为Scala类型检查器不知道,该查询和q和qc必须是相同的引用。

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

不使用@unchecked或asInstanceOf等的一个解决方案是将Result转换为QueryLike的类型参数。一般情况下,您不应该将抽象类型成员用于您想要“在上下文中”自由传递的内容。因此,在继承层次结构的某处将类型成员转换为类型参数有点奇怪。

所以这很好,正如编译器所知,他不必检查类型参数:

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可能必须能够确定两个稳定存取器何时相同。