So the scala compiler is complaining that a pattern match might not be exhaustive for the method foo and I wonder why. This is the code:
abstract class Foo {
def foo(that: Foo): Unit = (this, that) match {
case (Foo_1(), Foo_1()) => //case 1
case (Foo_1(), Foo_2()) => //case 2
case (Foo_2(), Foo_1()) => //case 3
case (Foo_2(), Foo_2()) => //case 4
// Compiler warning
}
def fooThis(): Unit = this match {
case Foo_1() => //do something
case Foo_2() => //do something
// Works fine
}
def fooThat(that: Foo): Unit = that match {
case Foo_1() => //do something
case Foo_2() => //do something
// Works fine
}
}
case class Foo_1() extends Foo
case class Foo_2() extends Foo
And this is the error:
Warning:(5, 32) match may not be exhaustive.
It would fail on the following inputs: (Foo(), _), (Foo_1(), _), (Foo_2(), _), (_, Foo()), (_, Foo_1()), (_, Foo_2()), (_, _)
def foo(that: Foo): Unit = (this, that) match {
Since this and that are of type Foo, and Foo can only be of type Foo_1 or Foo_2, the cases in foo are all possible combinations.
I added fooThis and fooThat for sake of completeness and to show that matching Foo_1 and Foo_2 suffices. The compiler message suggests that there are other types that can be matched (i.e. Foo and _).
So why is this warning shown?
Related:
Scala bug: fixed in 2.12.0-M4. My scala welcome message:
Welcome to Scala 2.12.1 (Java HotSpot(TM) Client VM, Java 1.8.0_131).
Scala bug: false match not exhaustive warning on (unsealed, sealed) tuple
EDIT
The compiler seems to complain as soon as you use tuples. If we add a dummy variable to fooThis as follows
def fooThis(): Unit = (this, Foo_1()) match {
case (Foo_1(),_) => //do something
case (Foo_2(),_) => //do something
}
we get the following compiler warning
Warning:(13, 27) match may not be exhaustive.
It would fail on the following input: (_, _)
def fooThis(): Unit = (this, Foo_1()) match {
The Scala compiler won't give exhaustive match warnings for non-sealed traits (like your Foo). This explains why fooThis and fooThat compile without warnings.
If you want warnings here (and you should, because they're better than MatchError exceptions at runtime) you have a couple of options:
Make Foo sealed. This creates an ADT, which is safe to pattern match against in the sense that you'll get exhaustivity warnings when you forget a case. Option is an ADT that you're probably familiar with from the standard library. Here, you've already got cases for both Foo_1 and Foo_2, so you won't get an exhaustivity warning. But if you ever forget either case, you will. You probably want to make Foo_1 and Foo_2 final while you're at it.
Leave Foo unsealed, use Typelevel Scala and enable its -Xlint:strict-unsealed-patmat warnings.
On the other hand, the Scala compiler will give exhaustive match warnings for final case classes like Tuple2, which is what you're matching against in your foo method.
To answer "why is the warning shown?", consider what happens if we do this:
case class Foo3() extends Foo
val foo3 = Foo3()
foo3.foo(foo3)
(Answer: it throws a MatchError at runtime.)
The warning is the Scala compiler's way of helping you avoid the exception at runtime. If you want to make the warning go away, you could:
Make Foo sealed (again, creating an ADT), preventing Foo3 from sneaking in elsewhere.
Add a wildcard case _ => ....
Make the match unchecked: ((this, that): #unchecked) match { ....
Don't do number 3, because it leaves you vulnerable to MatchErrors at runtime when someone introduces Foo3.
So, perhaps the question isn't really "why does the match in foo generate a warning", but "why doesn't the match in fooThis and fooThat generate a warning".
It seems making the abstract class sealed at least makes the compiler warning go away:
sealed abstract class Foo {
Though I'm not too sure why. It might be related to: https://issues.scala-lang.org/browse/SI-9351
Figuring out all subclasses of a class is called Class Hierarchy Analysis, and doing static CHA in a language with dynamic code loading is equivalent to solving the Halting Problem.
Plus, one of the goals of Scala is separate compilation and deployment of independent modules, so the compiler simply cannot know whether or not a class is subclassed in another module, because it never looks at more than one module. (After all, you could compile a module against the interface of some other module without that module even existing on your system!) That's why sealed requires all subclasses to be defined in the same compilation unit.
That's why the compiler doesn't show warnings, because its aware of the existing subclasses.
Related
Considering the following example:
object MatchDuckType {
trait Sup
class Sub1() extends Sup {
def v1: String = "a"
}
def example(v: Any) = {
v match {
case _: Sup { def v1: String } => println(1)
case _ => println(2)
}
}
def main(args: Array[String]): Unit = {
example(Sub1())
example(1)
}
}
The compiler gave the following error:
MatchDuckType.scala:16:12: the type test for xxx.MatchDuckType.Sup{v1: String} cannot be checked at runtime
This statement is clearly wrong, as Sup { def v1: String } can be easily checked by Java runtime reflection (by first getting the class of the term, then getting its method signatures). Why does the warning still exist? Is it a bug in compiler?
That this doesn't work isn't merely a "bug"; including such a thing in the language would require substantial design, specification, and implementation work.
The feature you were hoping for doesn't exist in either Scala 2 or Scala 3.
I found a feature request for this dating back to 2007 (!), but it never attracted much attention: https://github.com/scala/bug/issues/329
Even if the feature did exist, the feature would be platform-specific and limited in scope: it would only work on the JVM, not on JS or Native, and it would only work for the subset of refinement types that can be faithfully be represented in bytecode. That leaves out a lot of the Scala type system.
(At least without TypeTag, but TypeTag is Scala 2-only; the feature request on TypeTag-based matching is https://github.com/scala/bug/issues/6517 . In a Scala 3 context, consider trying to use match types instead?)
As for your remark about desugaring, pattern matches desugar to isInstanceOf, and the behavior of isInstanceOf is the same, so this isn't really about pattern matching per se, it's about type tests.
The following code:
object Test {
#inline def unapply(i: Int): Option[String] =
i match {
case 1 => Some("Got 1")
case 2 => Some("Got 2")
case 3 => throw new Exception("Should not test 3")
case _ => None
}
def test(i: Int) = i match {
case Test(k) => k
case 4 => "Another 4"
case _ => ""
}
}
Test.test(3)
results in the following error:
...
at Test$.unapply(<console>:13)
at Test$.test(<console>:17)
...
Note that it's clear where the error comes from. However, the error shows that the method unapply is not inlined as I wanted to.
How can I inline this unapply method? This is for performance reason as well as code reuse.
#inline only requests that the compiler try to inline a method, but it doesn't have to, and in some cases, it can't. I don't think there is a spec for exactly what can and cannot be inlined by the compiler (though I'd love to see one if there is), but I'd bet the compiler simply won't in this case, no matter what you do.
Generally, you would only want to inline something that is a constant or small and unlikely to change, anyway. Otherwise, many uses of Test.unapply will cause the compiled code to increase in size quickly, and changes to the inlined method will proliferate across everything that references it.
This answer from #RexKerr says enough.
Looking at the Scala doc for sealed classes, it says:
If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
I don't quite understand what they meant in this paragraph. My understanding is that if a switch case, doesn't cover all the possibilities, then we'll get a warning at compile time, saying we might get an error at run time. Is this correct?
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings, which is just silly, so I take it my understanding is incorrect. Someone care to elucidate, please?
What the paragraph is saying is that in-case you have a fixed hierarchy structure like this:
sealed trait Foo
class Bar extends Foo
class Baz extends Foo
class Zab extends Foo
Then when you pattern match on it, the compiler can infer if you've attempted to match on all possible types extending the sealed trait, in this example:
def f(foo: Foo) = foo match {
| case _: Bar => println("bar")
| case _: Baz => println("baz")
| }
<console>:13: warning: match may not be exhaustive.
It would fail on the following input: Zab()
def f(foo: Foo) = foo match {
^
f: (foo: Foo)Unit
Note the beginning of the document says:
A sealed class may not be directly inherited, except if the inheriting
template is defined in the same source file as the inherited class.
This is unique to Scala, and can't be done in Java. A final class in Java cannot be inherited even if declared inside the same file. This is why this logic won't work for String in Scala, which is an alias for java.lang.String. The compiler warning may only be emitted for Scala types that match the above criteria.
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings
Yes, if the selector has type String (except it isn't a sealed class, because that's a Scala concept and String is a Java class).
which is just silly
No. For strings, you just need a catch-all case, e.g.
val x: String = ...
x match {
case "a" => ...
case "b" => ...
case _ => ...
}
is an exhaustive match: whatever x is, it matches one of the cases. More usefully, you can have:
val x: Option[A] = ...
x match {
case Some(y) => ...
case None => ...
}
and the compiler will be aware the match is exhaustive even without a catch-all case.
The wildcard character allows us to cover all the scenarios.
something match {
case one => ...
case two => ...
case _ => ...
}
It is selected whenever all other cases don't match; that is, it is the default case. Further information here.
Define a list first:
val list = List(1,2,3)
Scala compiler gives warning (even if it can match):
list match {
case head :: tail => println(s"h:${head} ~ t: ${tail}")
}
Scala compiler won't give warning (even if it can't match):
list match {
case List(a,b) => println("!!!")
}
I can't understand the second one
The "match may not be exhaustive" warning is only given when pattern matching on a type that is a sealed class and you have cases for only a subset of the subclasses or objects. List is a sealed class with a subclass :: and a subobject Nil, something like:
sealed abstract class List[+T]
class ::[+T] extends List[+T]
object Nil extends List[Nothing]
If you have a match and don't have a case for :: and one for Nil, and also don't have a case that might match any List, Scala knows the match isn't exhaustive and will report it. A case _ would match anything and will prevent the warning. But List(a, b) also prevents the warning, because Scala doesn't know if it only matches some of the subclasses.
When you use List as an extractor, as in List(a, b), you are using the extractor List.unapplySeq to take apart the matched value. Scala doesn't try to make assumptions about how the extractor behaves, and therefore doesn't know that the match isn't exhaustive. Without knowing the implementation details of List.unapplySeq, there is no way to know it won't happily match everything and return the required two values.
I'm trying to understand what Scala does with Case Classes that makes them somehow immune to type erasure warnings.
Let's say we have the following, simple class structure. It's basically an Either:
abstract class BlackOrWhite[A, B]
case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]
case class White[A,B]( val right: B ) extends BlackOrWhite[A,B]
And you're trying to use it like this:
object Main extends App {
def echo[A,B] ( input: BlackOrWhite[A,B] ) = input match {
case Black(left) => println( "Black: " + left )
case White(right) => println( "White: " + right )
}
echo( Black[String, Int]( "String!" ) )
echo( White[String, Int]( 1234 ) )
}
Everything compiles and runs without any problems. However, when I try implementing the unapply method myself, the compiler throws a warning. I used the following class structure with the same Main class above:
abstract class BlackOrWhite[A, B]
case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]
object White {
def apply[A,B]( right: B ): White[A,B] = new White[A,B](right)
def unapply[B]( value: White[_,B] ): Option[B] = Some( value.right )
}
class White[A,B]( val right: B ) extends BlackOrWhite[A,B]
Compiling that with the -unchecked flag issues the following warning:
[info] Compiling 1 Scala source to target/scala-2.9.1.final/classes...
[warn] src/main/scala/Test.scala:41: non variable type-argument B in type pattern main.scala.White[_, B] is unchecked since it is eliminated by erasure
[warn] case White(right) => println( "White: " + right )
[warn] ^
[warn] one warning found
[info] Running main.scala.Main
Now, I understand type erasure and I've tried to get around the warning with Manifests (to no avail so far), but what is the difference between the two implementations? Are case classes doing something that I need to add in? Can this be circumvented with Manifests?
I even tried running the case class implementation through the scala compiler with the -Xprint:typer flag turned on, but the unapply method looks pretty much like I expected:
case <synthetic> def unapply[A >: Nothing <: Any, B >: Nothing <: Any](x$0: $iw.$iw.White[A,B]): Option[B] = if (x$0.==(null))
scala.this.None
else
scala.Some.apply[B](x$0.right);
I cannot give a complete answer, but I can tell you that even though the compiler generates an unapply method for case classes, when it pattern matches on a case class it does not use that unapply method. If you try -Ybrowse:typer using both builtin case matching and your unapply method, you will see a very different syntax tree is produced (for the match) depending on which is used. You can also browse the later phases and see that the difference remains.
Why Scala does not use the builtin unapply I am not sure, though it may be for the reason you bring up. And how to get around it for your own unapply I have no idea. But this is the reason Scala seems to magically avoid the problem.
After experimenting, apparently this version of unapply works, though I'm a bit confused about why:
def unapply[A,B](value: BlackOrWhite[A,B]): Option[B] = value match {
case w: White[_,_] => Some(w.right)
case _ => None
}
The difficulty with your unapply is that somehow the compiler has to be convinced that if a White[A,B] extends a BlackOrWhite[C,D] then B is the same as D, which apparently the compiler is able to figure out in this version but not in yours. Not sure why.
I can't give you the answer on the difference between case class match and unapply. However in their book (Odersky, Spoon, Venners) "Programming in Scala" 2nd chptr 26.6 "Extractors versus case classes" they write:
"they (case classes) usually lead to more efficient pattern matches
than extractors, because the Scala compiler can optimize patterns over
case classes much better than patterns over extractors. This is
because the mechanisms of case classes are fixed, whereas an unapply
or unapplySeq method in an extractor could do almost anything. Third,
if your case classes inherit from a sealed base class, the Scala
compiler will check our pattern matches for exhaustiveness and will
complain if some combination of possible values is not covered by a
pattern. No such exhaustiveness checks are available for extractors."
Which says to me that the two are more different than one would expect at first glance, however without being specific on what the exact differences are.