如何在不使用它的情况下与依赖类型进行模式匹配?
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)
}
由于模式匹配是在不同类型(Query
、CachedQuery
)上完成的,而不是依赖像 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 可能必须能够确定两个稳定访问器何时相同。
这很难表达,请让我举个例子:
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)
}
由于模式匹配是在不同类型(Query
、CachedQuery
)上完成的,而不是依赖像 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 可能必须能够确定两个稳定访问器何时相同。