Matching with custom combinations/operators

2020-03-20 05:44发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

There are two ::s (pronounced "cons") in Scala. One is the operator on Lists 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.