I know that you can do matching on lists in a way like
val list = List(1,2,3)
list match {
case head::tail => head
case _ => //whatever
}
so I started to wonder how this works. If I understand correctly, ::
is just an operator, so what's to stop me from doing something like
4 match {
case x + 2 => x //I would expect x=2 here
}
If there is a way to create this kind of functionality, how is it done; if not, then why?
Pattern matching takes the input and decomposes it with an unapply
function. So in your case, unapply(4)
would have to return the two numbers that sum to 4. However, there are many pairs that sum to 4, so the function wouldn't know what to do.
What you need is for the 2
to be accessible to the unapply
function somehow. A special case class that stores the 2
would work for this:
case class Sum(addto: Int) {
def unapply(i: Int) = Some(i - addto)
}
val Sum2 = Sum(2)
val Sum2(x) = 5 // x = 3
(It would be nice to be able to do something like val Sum(2)(y) = 5
for compactness, but Scala doesn't allow parameterized extractors; see here.)
[EDIT: This is a little silly, but you could actually do the following too:
val `2 +` = Sum(2)
val `2 +`(y) = 5 // y = 3
]
EDIT: The reason the head::tail
thing works is that there is exactly one way to split the head from the tail of a list.
There's nothing inherently special about ::
versus +
: you could use +
if you had a predetermined idea of how you wanted it to break a number. For example, if you wanted +
to mean "split in half", then you could do something like:
object + {
def unapply(i: Int) = Some(i-i/2, i/2)
}
and use it like:
scala> val a + b = 4
a: Int = 2
b: Int = 2
scala> val c + d = 5
c: Int = 3
d: Int = 2
EDIT: Finally, this explains that, when pattern matching, A op B
means the same thing as op(A,B)
, which makes the syntax look nice.
Matching with case head :: tail
uses an infix operation pattern of the form p1 op p2
which gets translated to op(p1, p2)
before doing the actual matching. (See API for ::
)
The problem with +
is the following:
While it is easy to add an
object + {
def unapply(value: Int): Option[(Int, Int)] = // ...
}
object which would do the matching, you may only supply one result per value. E.g.
object + {
def unapply(value: Int): Option[(Int, Int)] = value match {
case 0 => Some(0, 0)
case 4 => Some(3, 1)
case _ => None
}
Now this works:
0 match { case x + 0 => x } // returns 0
also this
4 match { case x + 1 => x } // returns 3
But this won’t and you cannot change it:
4 match { case x + 2 => x } // does not match
No problem for ::
, though, because it is always defined what is head
and what is tail
of a list.
There are two ::
s (pronounced "cons") in Scala. One is the operator on List
s and the other is a class, which represents a non empty list characterized by a head and a tail. So head
:: tail
is a constructor pattern, which has nothing to do with the operator.