Pattern matching with named fields


#1

When matching is done on a case class with a few field it works fine. One can use underscore to express ‘match all’ in a certain position.

case class Point(x: Int, y: Int)
val p = Point(1, 43)
p match {
  case Point(_, 43) => ... // matching only second parameter
  case Point(x, y) => ...
}

Matching quickly becomes tedious as number of fields grows:

{
  case Request(Method.Post, _, _, _, _) => ...
}

Moreover if a new field is added to the case class all match cases have to be adjusted.
I believe matching only certain fields referring them by name would solve the problem:

{
  case Request(method = Method.Post) => ...
}

Is there reason not to have this feature in Scala?
As I understand at the moment pattern matching for case classes is implemented on top of tuples. Would that mean that for the proposed feature language needs Records first?


#2

This sounds like a good SIP proposal


#3

I’d prefer something like:

{
  case Request(method = Method.Post, _*) => ...
}

To make it clear that there are more arguments after it. I like this idea, by the way. The only drawback I see is that this is yet another place where changing a parameter name in the target ADT would produce a source incompatibility downstream. Perhaps not such a big deal considering that all case classes seem to have this issue (copy and unapply are usually broken too).


#4

I could also hear the argument that when using named syntax, you should never expect it to be complete necessarily.

Here’s a different question: Would you be able to write a custom named-argument extractor? What would its type signature be and how would it work?


#5

there is previous discussion on this at https://github.com/scala/bug/issues/6524


#6

The linked scala-debate thread is so old, someone was posting from their blackberry.

There were a couple of distractions like use of default args. Ignoring that, one simplification:

case C(id = p) is rewritten case C(id = id @ p) with usual temporaries and reordering.

case class C(a = v, b, c)
case C(b = 42) means case C(_, b @ 42, _)

where the default arg v does not mean compute v and match against C(_ @ V0, b @ 42, _).

and since they’re mucking with imports,

case import C(b = 42) means case x @ C(b = 42) => import x._ or import x.{a, b, c}

as another way to avoid tedious variables.


#7

That was my question. For custom extractors Records should be implemented first I’m afraid. That would allow the following:

object Request {
  def unapply(req: Request): Option[(method: String, headers: Map[String, String], body: InputStream)] = ...
}

Without Records the implementation will be case class specific and won’t support custom extractors.
Unfortunately Records are not planned for Scala 3.