How to systematically avoid unsafe pattern matching in Scala?
Asked Answered
H

2

12

Consider the following broken function:

def sum (list : Seq[Int]) : Int = list match {
  case Nil => 0
  case head :: tail => head + sum(tail)
}

Here, the function was supposed to work with a List[Int], but was refactored to accept Seq[Int] instead, thus becoming broken without the compiler noticing.

This gaping hole in Scala's incomplete pattern match detection makes it next to useless.

I want to have a way of systematically detecting such problems. Specifically, I'd like the compiler to emit an error/warning on every instanceof-guided pattern match, i.e. I only want to allow pattern matches on sealed hierarchies and on custom matchers.

Are there existing compiler options/plugins for doing conservative (as opposed to arbitrary) checks of pattern matching safety?

Hau answered 18/1, 2012 at 14:20 Comment(1)
I use this match example with List very often, too. My advice in this case is to use list.toList match ...(because you know in advance you probably will refactor some code (perhaps not this method) later on and the match cases are only working with List), then you can refactor the method parameter to Seq without broken code.Argolis
C
3

Have a look at this answer by M. Odersky.

Summary

Checks on matches of non-sealed hierarchies are doable, not trivial and not (yet) implemented.

Collaboration answered 18/1, 2012 at 14:52 Comment(1)
That misses the point. I don't ask the compiler to be omniscient or even clever. I just want the check to become conservative, meaning that out of "Safe / Unsafe / Unknown" analyses only the first one would be admitted without warnings.Hau
A
3

Nil and :: are clearly ways to construct a List, but not all Sequences happen to be Lists, so one would expect the Scala type checker to reject this program as ill-typed. Right?

Wrong. Try this and you'll see what I mean:

def sum (list : Seq[Int]) : Int = list match {
  case Nil => 0
  case head :: tail => head + sum(tail)
  case _ => -1
}

> sum(Array(1,2,3).toSeq)
res1: Int = -1
> sum(List(1,2,3))
res2: Int = 6

So you see, some Sequences might be able to be deconstructed with Nil and ::, so those which can, will. Those which can't will fail the pattern match and move on, trying the next match. Nil and :: are sufficient to cover all the possibilities for List, but not for Seq. There is a tradeoff here between subtyping, convenience, and type safety. The solution for now is: be more careful when you refactor.

Almeria answered 18/1, 2012 at 17:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.