2
votes

The context of my question is similar to some others asked in the forum, but I cannot find an exact match and it still remains a mystery to me after viewing those answers. So I appreciate it if someone can help. The context of my question is to match a singleton class object using a pattern match.

For example, if I am implementing a list structure of my own, like this

// An implementation of list
trait AList[+T] // covariant 
case class Cons[+T](val head: T, val tail: AList[T]) extends AList[T] 
case object Empty extends AList[Nothing] // singleton object

// an instance of implemented list
val xs = Cons(1, Cons(2, Cons(3, Empty)))

// pattern matching in a method - IT WORKS!
def foo[T](xs: AList[T]) = xs match {
    case Empty => "empty"
    case Cons(x, xss) => s"[$x...]"
}
println(foo(xs)) // => [1...]

// pattern matching outside - IT RAISES ERROR:
// pattern type is incompatible with expected type;  
// found   : Empty.type  
// required: Cons[Nothing]
val r: String = xs match {
    case Empty => "EMPTY"
    case Cons(x, xss) => s"[$x...]"

}
println(r) // does NOT compile

To me they look like the same "matching" on the same "objects", how come one worked and the other failed? I guess the error had something to do with the different of matching expr in and out of methods, but the message given by the compiler was quite misleading. Does it mean we need to explicitly cast xs like xs.asInstanceOf[AList[Int]] when "matching" outside?

2

2 Answers

4
votes

Compiler tells you that type of xs is Cons and it can't be Empty, so your first case is pointless.

Try this:

val r: String = (xs: AList[Int]) match {
    case Empty => "EMPTY"
    case Cons(x, xss) => s"[$x...]"
}

Or this:

val ys: AList[Int] = xs
val r: String = ys match {
    case Empty => "EMPTY"
    case Cons(x, xss) => s"[$x...]"
}

In this case compiler don't knows that case Empty is pointless.

It's exactly what you are doing with def foo[T](xs: AList[T]) = .... You'd get the same compilation error with def foo[T](xs: Cons[T]) = ....

In this particular example valid and exhaustive match looks like this:

val r: String = xs match {
    // case Empty => "EMPTY" // would never happened.
    case Cons(x, xss) => s"[$x...]"
}

Addition: you should make your AList trait sealed:

sealed trait AList[+T]

It allows compiler to warn you on not exhaustive matches:

val r: String = (xs: AList[Int]) match {
  case Cons(x, xss) => s"[$x...]"
}
<console>:25: warning: match may not be exhaustive.
It would fail on the following input: Empty
       val r: String = (xs: AList[Int]) match {
                          ^
1
votes

The parameter of foo is a AList[T], so in the first case the matching is being done on a AList[T]. In the second case the matching is being done on a Cons[+T].

Basically matching is done on a object type, not on a object.