tags:

views:

796

answers:

4

Hi,

I have two questions regarding the '::' case class.

:: can be used as

case head :: tail => ...

How does it work? Meaning, what is exactly the flow that Scala uses to match a List instance with the :: case class? Given that I have a class MyClass, with operator op, can I create a case class named op that I can use as:

case foo op bar => ....

?

+1  A: 
     scala> abstract class Stack {
     |     def push(n :Int):Stack
     | }
      defined class Stack

     scala> final case class push(st :Stack,hd :Int) extends Stack {
     |     override def push(n :Int):Stack = new push(this,n)
     | }
     defined class push

     scala> object NullStack extends Stack {
     |     override def push(n :Int):Stack = new push(null,n)
     | }
     defined module NullStack

     scala> val s = NullStack.push(1).push(2)
     s: Stack = push(push(null,1),2)

     scala> def test(s :Stack) = s match { case st push i => println(st +"push " + i) }
     test: (Stack)Unit

     scala> test(s)
     push(null,1)push 2
Eastsun
great answer! so the trick is that List#:: returns an instance of the case class ::.
IttayD
+1  A: 

Check out this question.

Daniel
+2  A: 

It's detailed in page 301 of Programming in Scala, About pattern matching on Lists.

The "cons" pattern x :: xs is a special case of an infix operation pattern. You know already that, when seen as an expression, an infix operation is equivalent to a method call. For patterns, the rules are different: When seen as a pattern, an infix operation such as p op q is equivalent to op(p, q). That is, the infix operator op is treated as a constructor pattern. In particular, a cons pattern such as x :: xs is treated as ::(x, xs). This hints that there should be a class named :: that correspond to the pattern constructor. Indeed there is such a class. It is named scala.:: and is exactly the class that builds non-empty lists.

eed3si9n
+2  A: 

Actually, the fact that :: is a case class is only half of the answer. The reason this works in pattern matching is that there is an extractor for object ::, which is generated automatically when a case class is defined. Conveniently, ::.unapply returns a List, because :: extends List. If you want to use the same trick for Lists, though, you won't be able to extend List, because it's final. What you can do is define an object with the appropriate unapply method, which has the expected return signature. For instance, to match the last element of a list, you can do:

object ::> {def unapply[A] (l: List[A]) = Some( (l.init, l.last) )}

List(1, 2, 3) match {
  case _ ::> last => println(last)
}

(1 to 9).toList match {
  case List(1, 2, 3, 4, 5, 6, 7, 8) ::> 9 => "woah!"
}
(1 to 9).toList match {
  case List(1, 2, 3, 4, 5, 6, 7) ::> 8 ::> 9 => "w00t!"
}

The extractor must return an Option, which contains a tuple of the two deconstructed elements.

vdichev
I don't think unapply applies here scala> val l = List(1, 2 , 3)l: List[Int] = List(1, 2, 3)scala> scala.::.unapply(l)<console>:6: error: type mismatch; found : List[Int] required: ::[?]val r = scala.::(1, Nil)r: ::[Int] = List(1)scala> scala.::.unapply(r) res7: Some[List[Int]] = Some(List())so unapply works only if actually constructed by the case class, not for a general list.
IttayD
There's nothing special about the unapply which a case class creates. Note that List is abstract and actually any non-empty list is an instance of scala.::List(1).isInstanceOf[::[Int]]Which means that what you're matching in patterns is actually instances of scala.:: (unless it's Nil). Notice also that as soon as you only redefine unapply for ::, pattern matching for Lists breaks:object :: {def unapply = false}
vdichev