添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

At least while I’m writing the program, I’m not yet 100% convinced that my pattern matching is exhausting. In this case what is the correct way to intentionally trigger an error at runtime if the unexpected case arrives? I’d like to do something like the following call to error . Is there a good idiom to use?

    val r:(Int,A) = stack match {
      case Nil => error(s"I don't think this will ever happen: objs=$objs, init=$init")
      case head::tail => tail.foldLeft(head){case (a1,(_,a2)) => (0,f(a1,a2))}
              

I don’t know if this is the correct way, but what I do is throw an exception, e.g.

val r:(Int,A) = stack match {
      case Nil => throw new Exception(s"I don't think this will ever happen: objs=$objs, init=$init")
      case head::tail => tail.foldLeft(head){case (a1,(_,a2)) => (0,f(a1,a2))}

To make debugging easier, I usually have a custom class extending Exception which has as arguments relevant stuff in the environment (an example copied from my code is below.

class ApplnFailException(val func: Term, val arg: Term)
      extends IllegalArgumentException(
        s"function $func with domain(optional) ${ApplnFailException
          .domOpt(func)} cannot act on given term $arg with type ${arg.typ}"
    def domOpt: Option[Typ[Term]] = func match {
      case fn: FuncLike[u, v] => Some(fn.dom)
      case _                  => None
    def argType: Typ[Term] = arg.typ

regards,
Siddhartha

As a rule of thumb, I recommend putting the expected cases at the top where possible, and a catch-all case _ => at the bottom for unexpected cases, if you’re not sure about whether exhaustivity checking will work in your situation.

That said, if stack is a List here, then what you have looks fine – it’s either a cons cell or Nil, so you’ve covered your bases…

jducoeur:

As a rule of thumb, I recommend putting the expected cases at the top where possible, and a catch-all case _ => at the bottom for unexpected cases, if you’re not sure about whether exhaustivity checking will work in your situation.

Hmmm, that it interesting, because it is the opposite of my rule of thumb. My rule of thumb is DON’T put in an _ case for unexpected cases, because that will circumvent the compiler’s ability to warn me about missing cases. I generally try to make the pattern matching as specific as possible, to get the maximum benefit from the compiler diagnostics.

You don’t need to import anything, and can just prefix it with the required package, i.e.

sys.error("My message")

Regarding the initial question on the proper way of intentionally fail in a pattern-matching, I think using sys.error is a good trade-off: it has a pretty light-weight syntax, and it doesn’t pollute your code with lots of checked exceptions. And its return type Nothing makes it very easy to use.

I have been looking for alternatives, either built-in or available in the standard library, but did not find anything that was as concise as sys.error. More experienced users may have further advice.

You could also consider using requires/ensures clauses to better assess your program logic, but that wouldn’t address the problem of making your pattern-matchings exhaustive.

Agreed.

An unqualified wildcard match breaks exhaustivity checking and I’ve seen it lead to production issues more than once when accidentally (or intentionally!) left in place, so i tend to consider it an anti-pattern. Especially with ADTs, when the base type is modified, you WANT the use sites to be updated (or at least looked at).

I will propose an exception to this rule. In untyped Akka code, the messages are of type Any and messages that aren’t handled are just silently ignored. Having a last case that accepts anything any prints out a message saying that you got a message you didn’t handle can save tons of time for finding and tracking down bugs. Of course, this exception to the rule disappears if you being using Akka Typed.

jimka:

My rule of thumb is DON’T put in an _ case for unexpected cases, because that will circumvent the compiler’s ability to warn me about missing cases.

Hence the “if you’re not sure about whether exhausivity checking will work”. When you have a pattern that should be exhaustive (typically a sealed trait), then I agree with you. But you often aren’t in a situation where that is possible, so I generally prefer to put in the case _, and handle the error in a custom way, instead of risking a MatchError.

(I may be more sensitive to this than average – my code is extremely Akka-heavy, and as @MarkCLewis points out, that’s the absolute worst-case situation when it comes to exhaustivity checking.)

@jducoeur, perhaps another common exception is tuple types.
Here is a case where I WANT a match error in case I’ve forgotten a case.

object And extends BinaryOperation {
  def apply():Bdd = BddTrue
  def apply(bdd:Bdd):Bdd = bdd
  def apply(b1: Bdd, b2: Bdd): Bdd = {
    (b1, b2) match {
      case (b1, b2) if b1 eq b2 => b1
      case (BddTrue, _) => b2
      case (BddFalse, _) => BddFalse
      case (_, BddTrue) => b1
      case (_, BddFalse) => BddFalse
      case (b1: BddNode, b2: BddNode) => Bdd.bddOp(this, b1, b2)

Here is another case. By the way, in both of these cases it would be even better if the compiler would warn me about unreachable code, in case I’ve messed up in the order of the clauses.

  def treeReduce[A](objs:List[A],init:A,f:(A,A)=>A):A = {
    def recur(stack:List[(Int,A)],remaining:List[A]):A = {
      (stack,remaining) match {
        case ((a,x)::(b,y)::tail,_) if a == b => recur((a+1,f(x,y))::tail,remaining)
        case (_,o::os) => recur((1,o)::stack,os)
        case ((_,o)::Nil,Nil) => o
        case ((_,x)::(_,y)::tail,Nil) => recur((0,f(x,y))::tail, Nil)
    recur((1,init)::Nil,objs)
              

That’s fine. Don’t get me wrong – I’m not saying “You must always have a catch-all case clause”. I’m just saying that I prefer custom error handlers to the catch-all MatchError (so that production failures get handled appropriately), if I think that a missing case is even possible. (Which it shouldn’t be for well-behaved ADTs.)

Well, there is one reason to add such clauses: stop the compiler from warning you of inexhaustive matching. So I do sometimes have clauses like:

case _ => throw new RuntimeException(“This should ever happen. Must be a bug in the code.”)

  • Do not put in a catch all when designing the patterns.
  • Then, if need or whimsy takes you, add patterns that fill in your gaps up to and including a catch all.
  • For impossible cases it is useful to use
  • sys.error("unreachable code - Here be dragons.")
    

    or some other exception with whatever message you like. Your impossible case today might not be so down the line.