简体   繁体   English

如何使Scala的类型系统捕获此MatchError?

[英]How to make Scala's type system catch this MatchError?

I've defined an ordering for Seq[Seq[T]] such that it's a normal lexicographic ordering except all items (sub-sequences) are reversed first (so that C,B,A comes before A,B,C but after A,B,A ): 我已经定义了Seq[Seq[T]]的排序,以便它是正常的字典顺序,除了所有项(子序列)先反转(以便C,B,AA,B,C之前A,B,CA,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)
  }
}

This used to be defined on List[List[T]] at first but I later realized I want it for all Seq[Seq[T]] ; 最初是在List[List[T]]上定义的,但后来我意识到我希望对所有Seq[Seq[T]] this is why I initially left in the Nil s in the pattern matching block, while failing to realize Nil never matches eg an empty Array . 这就是为什么我最初在模式匹配块中保留Nil的原因,而未能意识到Nil从不匹配,例如一个空Array

Later I tried to run this block of code: 后来我尝试运行以下代码块:

// 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)

This compiles just fine but results in the following error at runtime: 这样编译就可以了,但是在运行时会导致以下错误:

scala.MatchError: (WrappedArray(Estonia, Tallinn),List(Estonia)) (of class scala.Tuple2)

— while I understand the cause of the error perfectly well, what I fail to understand (or at least accept) is, if the Ordering is successfully defined on all Seq[Seq[T]] , yet a superficially valid but obviously incompatible (in terms of how doCompare is defined) Seq[Seq[T]] (ie Array[List[String] | Array[String]] ) attempt to use that ordering results in no static type errors or even warnings whatsoever. —尽管我非常清楚地理解了错误的原因,但我无法理解(至少是接受)的是,如果在所有Seq[Seq[T]]上都成功定义了Ordering ,但表面上有效,但显然不兼容(在有关如何定义doCompare条款) Seq[Seq[T]] (即Array[List[String] | Array[String]] ))尝试使用该顺序不会导致任何静态类型错误甚至警告。

Is this caused by the fact that the pattern matching code is not statically verified to cover all possible "instances" of Seq[Seq[T]] and that it only handles the List case? 这是由于模式匹配代码未经过静态验证以覆盖Seq[Seq[T]]所有可能“实例”并且仅处理List情况的事实引起的吗? If yes, what are the currently available workarounds to achieving type safety in such cases? 如果是,那么在这种情况下实现类型安全的当前可用的解决方法是什么? Is Scalaz something to be looked at yet again for a decent solution? Scalaz是否需要再次考虑以寻求一个体面的解决方案?

PS I'm aware I could easily do away with a solution that works for all Seq[Seq[T]] by not using pattern matching and resorting to head and tail with an if-else block (or case statements if guards, which is only superficially nicer), but I'm keen to learn how to make the most of out Scala's type capabilities (AFAIK F# and Haskell catch these errors for breakfast); PS我知道我可以通过不使用模式匹配并使用if-else块(或case语句,如果有后卫)不使用模式匹配和headtail来轻松解决适用于所有Seq[Seq[T]]的解决方案只是表面上更好),但我很想学习如何充分利用Scala的类型功能(AFAIK F#和Haskell可以在早餐时捕获这些错误); not to mention pattern matching is by far more elegant and readable. 更不用说模式匹配了,更优雅,更易读。

This might be closer: 这可能更接近:

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 }
                                                                           ^

There is a known syndrome when the type parameter is on the class instead of the method. 当类型参数位于类而不是方法上时,存在一个已知的综合症。

It has surfaced on the ML recently. 它最近在ML上浮出水面。 Here. 这里。

Comparing to: 比较:

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

Sorry to be lazy, but off to bed. 对不起,我很懒,但是上床睡觉了。

Scala compiler produces non-exhaustive match warnings only for sealed types, which Seq isn't. Scala编译器仅针对sealed类型生成非穷举的匹配警告,而Seq不会。 AFAIK, there is no way to force it to check, like there is for tail recursion. AFAIK,没有办法强制检查它,就像尾部递归一样。

(AFAIK F# and Haskell catch these errors for breakfast) (AFAIK F#和Haskell在早餐时发现了这些错误)

Haskell doesn't have an equivalent to Seq ; Haskell没有Seq的等效项; the code you used to have with List is the one which is equivalent to Haskell (modulo laziness), and Scala does catch the error in this case. List一起使用的代码是等效于Haskell(模懒度)的代码,在这种情况下Scala 确实捕获了错误。 I don't know F# well, but looking at http://msdn.microsoft.com/en-us/library/dd547125.aspx it seems that pattern matching a general IEnumerable<T> isn't supported. 我不太了解F#,但是从http://msdn.microsoft.com/zh-cn/library/dd547125.aspx看,似乎不支持与常规IEnumerable<T>匹配的模式。

Use code: 使用代码:

    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)
            }
   }

As I mention is comment Nil is not the same type as Seq(). 正如我提到的那样,注释Nil与Seq()的类型不同。 For example WrappedArray is not a List. 例如,WrappedArray不是列表。 And when you use x :: xs - it is matched as List. 当您使用x :: xs -它与列表匹配。 Array("Tallinn", "Estonia") is converted to WrappedArray. Array("Tallinn", "Estonia")转换为WrappedArray。 Always use +: in pattern matching when you use Seq 使用Seq时,请始终在模式匹配中使用+:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM