[英]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.