[英]How to make Scala's type system catch this MatchError?
我已经定义了Seq[Seq[T]]
的排序,以便它是正常的字典顺序,除了所有项(子序列)先反转(以便C,B,A
在A,B,C
之前A,B,C
在A,B,A
之后) A,B,A
):
implicit def ReverseListOrdering[T: Ordering] = new Ordering[Seq[T]] {
override def compare(xs1: Seq[T], xs2: Seq[T]) =
doCompare(xs1.reverse, xs2.reverse)
private def doCompare(xs1: Seq[T], xs2: Seq[T]): Int = (xs1, xs2) match {
case (Nil, Nil) => 0
case (x :: _, Nil) => 1
case (Nil, x :: _) => -1
case (x :: xs, y :: ys) =>
val a = implicitly[Ordering[T]].compare(x, y)
if (a != 0) a else doCompare(xs, ys)
}
}
最初是在List[List[T]]
上定义的,但后来我意识到我希望对所有Seq[Seq[T]]
; 这就是为什么我最初在模式匹配块中保留Nil
的原因,而未能意识到Nil
从不匹配,例如一个空Array
。
后来我尝试运行以下代码块:
// the Seq[String] declarations are needed; otherwise sample` will be Array[Object] for some reason
val sample = List(
List("Estonia"): Seq[String],
Array("Tallinn", "Estonia"): Seq[String],
List("Tallinn", "Harju", "Estonia"): Seq[String])
println(sample.sorted)
这样编译就可以了,但是在运行时会导致以下错误:
scala.MatchError: (WrappedArray(Estonia, Tallinn),List(Estonia)) (of class scala.Tuple2)
—尽管我非常清楚地理解了错误的原因,但我无法理解(至少是接受)的是,如果在所有Seq[Seq[T]]
上都成功定义了Ordering
,但表面上有效,但显然不兼容(在有关如何定义doCompare
条款) Seq[Seq[T]]
(即Array[List[String] | Array[String]]
))尝试使用该顺序不会导致任何静态类型错误甚至警告。
这是由于模式匹配代码未经过静态验证以覆盖Seq[Seq[T]]
所有可能“实例”并且仅处理List
情况的事实引起的吗? 如果是,那么在这种情况下实现类型安全的当前可用的解决方法是什么? Scalaz是否需要再次考虑以寻求一个体面的解决方案?
PS
我知道我可以通过不使用模式匹配并使用if-else块(或case
语句,如果有后卫)不使用模式匹配和head
和tail
来轻松解决适用于所有Seq[Seq[T]]
的解决方案只是表面上更好),但我很想学习如何充分利用Scala的类型功能(AFAIK F#和Haskell可以在早餐时捕获这些错误); 更不用说模式匹配了,更优雅,更易读。
这可能更接近:
scala> def cmp[A, B[_] <: Seq[_]](xs: B[A], ys: B[A]) = (xs, ys) match { case (Nil, Nil) => true }
<console>:7: error: pattern type is incompatible with expected type;
found : scala.collection.immutable.Nil.type
required: B[?A1] where type ?A1 (this is a GADT skolem)
def cmp[A, B[_] <: Seq[_]](xs: B[A], ys: B[A]) = (xs, ys) match { case (Nil, Nil) => true }
^
当类型参数位于类而不是方法上时,存在一个已知的综合症。
它最近在ML上浮出水面。 这里。
比较:
scala> (null: Seq[_]) match { case _: Nil.type => true }
scala.MatchError: null
... 33 elided
scala> (null: List[_]) match { case _: Nil.type => true }
<console>:8: warning: match may not be exhaustive.
It would fail on the following input: List(_)
(null: List[_]) match { case _: Nil.type => true }
^
scala.MatchError: null
... 33 elided
scala> (null: List[_]) match { case Nil => true }
<console>:8: warning: match may not be exhaustive.
It would fail on the following input: List(_)
(null: List[_]) match { case Nil => true }
^
scala.MatchError: null
... 33 elided
对不起,我很懒,但是上床睡觉了。
Scala编译器仅针对sealed
类型生成非穷举的匹配警告,而Seq
不会。 AFAIK,没有办法强制检查它,就像尾部递归一样。
(AFAIK F#和Haskell在早餐时发现了这些错误)
Haskell没有Seq
的等效项; 与List
一起使用的代码是等效于Haskell(模懒度)的代码,在这种情况下Scala 确实捕获了错误。 我不太了解F#,但是从http://msdn.microsoft.com/zh-cn/library/dd547125.aspx看,似乎不支持与常规IEnumerable<T>
匹配的模式。
使用代码:
implicit def ReverseListOrdering[T: Ordering] = new Ordering[Seq[T]] {
override def compare(xs1: Seq[T], xs2: Seq[T]) =
doCompare(xs1.reverse, xs2.reverse)
private def doCompare(xs1: Seq[T], xs2: Seq[T]): Int = (xs1, xs2) match {
case (Seq(), Seq()) => 0
case (x +: _, Seq()) => 1
case (Seq(), x +: _) => -1
case (x +: xs, y +: ys) =>
val a = implicitly[Ordering[T]].compare(x, y)
if (a != 0) a else doCompare(xs, ys)
}
}
正如我提到的那样,注释Nil与Seq()的类型不同。 例如,WrappedArray不是列表。 当您使用x :: xs
-它与列表匹配。 Array("Tallinn", "Estonia")
转换为WrappedArray。 使用Seq时,请始终在模式匹配中使用+:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.