More useful pattern matching

Look, I am not trying to be antagonistic here. But my reading of the language specification is that patterns are not expressions, they just try to look similar:
Expressions | Scala 3.4
Pattern Matching | Scala 3.4

I think we have different reasons on why this might be a useful feature to have, so I’ll not distract the discussion with my view anymore.

1 Like

Yes, that is technically correct. Nevertheless, in a pattern like Right(foo), the identifier Right refers to an object whose unapply method is going to be called, and I find it a strange restriction that arbitrary expressions are not allowed there.

The main blockage is to avoid the ambitious. For example:

case A(x)(y)(z) => …

we can’t tell which one applies:

case A.unapply(x)(y)(z)
case A.apply(x).unapply(y)(z)
case A.apply(x)(y).unapply(z)

2 Likes

Tbh I can’t follow your train of thought here. case A.unapply(x)(y)(z) or case A.apply(x).unapply(y)(z) is not valid syntax today, so I don’t know what these are supposed to mean.

I also think I’ve specified how the “multiple parameter lists for unapply approach” would work: the last parameter list is the one that the scrutinee is passed to. So assuming an extractor like this…

object A:
  def unapply(x: Any)(y: Any)(z: Any): Option[Any] = ???

…, these two would be equivalent:

a match
  case A(x)(y)(z) => …
// equivalent to
val P = A.unapply(x)(y)
a match
  case P(z) => …

That said, I favour the other approach anyway: use a syntax, such as {}, in the position where currently only the identifier of an extractor object is allowed. Then the above would be

a match
  case {A(x)(y)}(z) => …

This is pretty straight-forward and unambiguous, and it makes it much easier to use any existing classes or functions designed to create extractor objects. It’s also visually unappealing, which is why I’d love any ideas on how to make it prettier. The best I could come up with was something like

a match
  case (z) with A(x)(y) => …

I wonder how the Scala community feels about that and if they have some other ideas.

Syntax-wise I like the case {Expr}(x, y) => idea. It’s a bit similar to string interpolation s"foo${Expr}bar". It’s pattern interpolation.

Including the expression hoisting might make the feature a bit too complicated though. I think it’s better if you have to be explicit about that, just like how you have to be explicit about all other expressions of which you want to store and reuse the result.

1 Like

I consider the hoisting thing to be essential. There’s nothing worse than a feature feature that rewards you with convenience for doing the wrong thing, and using this feature with regular expressions is clearly very convenient. But unless you do the hoisting thing, you would compile the Regex pattern over and over again, which is clearly the wrong thing and a performance pitfall that I want to avoid. One shouldn’t be forced to choose between convenience and efficiency.
I see your point that this hoisting behaviour would be different from how expressions are usually handled in Scala. But I also think that extractor objects are a special case as I’m willing to bet that the vast majority of them are completely static, they’re really more part of the code than part of the data. For these reasons, I think the best way for this feature to work is to only allow access to static data (i. e. package level symbols) and have every attempt to access local symbols fail with a compilation error. That would make the feature efficient as well as predictable, and in cases where access to local symbols is required, users can always fall back to the current solution.