简体   繁体   中英

Understanding Pattern Matching with Sub-classes

Lift has the Box case class.

I wrote the following method to pattern match on a Box[A] :

scala> import net.liftweb.common._
import net.liftweb.common._

scala> def foo[A](box: Box[A]) = box match { 
     |   case Empty | Failure(_, _, _) => true
     |   case Full(_)                  => false
     | }
foo: [A](box: net.liftweb.common.Box[A])Boolean

I wrote this method to learn if ParamFailure , which is a sub-class of Failure , would pattern match on the Failure(_, _, _) case.

scala> val pf: Box[String] = ParamFailure("a", Empty, Empty, "blah")
pf: net.liftweb.common.Box[String] = ParamFailure(a, Empty, Empty, blah)

And, it did.

scala> foo(pf)
res9: Boolean = true

It's not clear to me why ParamFailure would match to Failure(_, _, _) . Why is that?

This is the whole point of inheritance. If S is a subclass of C , then you should be able to use S absolutely everywhere that you use C (this is called the Liskov Substitution Principle ).

Pattern matching is included.

Now, if you specifically want to tell if you have an S as opposed to a C , you can check for it:

class C {}
class S extends C {}
val c: C = new S
c match {
  case s: S => println("Actually, I was an S")
  case _ => println("Guess I was some other kind of C")
}

But if you ask if it's a C , the answer is yes:

c match {
  case c2: C => println("Yes, of course I am a C!")
  case _ => println("This would be super-weird.")
}

Again, adding pattern matching changes nothing here; whether you know the type and then pull out the parameters by hand, or whether Scala helpfully gives you identifiers for them, it works the same way.

case class P(p: Boolean) {}
object T extends P(true) {}
val p: P = T
p match {
  case P(tf) => println(tf)
  case _ => println("You will never reach here.")
}

This is just how case classes are defined in the spec .

The extractor takes an instance of the case class (that is, the unapply method takes a C) and returns the elements of the first parameter list.

You could imagine other definitions, such as requiring that the erased type is exactly the same, or at least that productArity is the same.

Pattern matching is defined as testing for a shape , and not only or primarily as a type test or equality test.

In fact, the spec for constructor patterns doesn't directly address subtyping:

The pattern matches all objects created from constructor invocations c(v1,…,vn)

Of course, subclass construction necessarily invokes that constructor.

Back when a case class could extend another case class , there might have been a higher expectation that pattern matching would distinguish subclasses, but that was before my time:

$ scala27
Welcome to Scala version 2.7.7.final (OpenJDK 64-Bit Server VM, Java 1.6.0_33).
Type in expressions to have them evaluated.
Type :help for more information.

scala> case class A(i: Int)                       
defined class A

scala> case class B(j: Int, s: String) extends A(j)
defined class B           

scala> (B(7,"hi"): Any) match { case A(7) => 1 case B(_,_) => 2 }
res1: Int = 1

scala> B(7,"hi").productArity                      
res2: Int = 2

I know from grammar school that some camels have one hump and others have two.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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