Pre SIP: Allow `,` in pattern alternatives

#1

I propose to migrate from bars to commas` as a way to separate pattern alternatives. I.e. instead of

x match { case A | B | C => ... }

use the syntax

x match { case A, B, C => ... }

The main reason to do the syntax switch is that it avoids an annoying feature interaction with union types. For instance, consider:

x match { case _: A | B => ... }

Is this a single pattern that tests whether x is of type A | B, or are these two patterns matching
either an x of type A or an x equal to the value B? The way it’s implemented now it is the second meaning, since that maintains backwards compatibility with Scala 2. But arguably the first interpretation is more natural, so people will be surprised that it does not work. To get the first meaning, they have to write

x match { case _: (A | B) => ... }

instead, but realizing this is not obvious. Switching from (|) to (,) avoids that problem.

Other reasons for the comma syntax:

Backwards compatibility and migration considerations

A way to migrate to the new syntax could be:

  • Allow (,) as an alternative to (|) in Scala 3.0
  • Deprecate (|) in Scala 3.1
  • Remove (|) in Scala 3.2 and at the same time allow union types in patterns to be written without enclosing parentheses.
  • Under -strict mode we can switch immediately to the 3.2 rules.

If Scala 2.14 would already allow (,) we could move the migration forward one cycle, i.e. deprecate | in 3.0 and remove it in 3.1.

I’d be interested in people’s opinions on this.

9 Likes
#2

I like it. I was wondering if we should use || instead of | for this purpose (this would also make && natural for combining patterns), but I was worried that something like case true || false would be too confusing. This syntax avoids that, however here’s a minor issue with it:
Right now, one can write:

object A
object B

val (A | B) = A: Any

And this couldn’t be replaced by either val (A, B) = A: Any or val A, B = A: Any since both of those things already mean something different (although I’d be happy to remove the val x,y,z = syntactic form from the language).

#3

Ah, I had not thought of the interaction with val. Or, more realistically, nested patterns:

   x match {
     case List(1 | 2, 3, 4) => ...
   }

That’s a feature that could not migrate easily. So, I fear the whole thing is a non-starter.

1 Like
#4

The conflict could also be fixed by changing types operators to use words instead of symbols. Something like : A or B.

#5

I have no idea what that means nor have I ever seen it. What’s the use case?

#6

@Sciss It’s a pattern definition, you can write val List(x) = List(1) to assign 1 to x for example. The one I wrote is an edge case where the pattern definition does not actually define anything.

#7

Ok, then this is what I understood - but then it isn’t really a “use case” because you cannot do anything with that kind of match.

#8

Martin’s reply has a useful example of it where he points out this likely kills the proposal.

1 Like
#9

Ok so , is out, but perhaps we can find some other syntax that works ? I think I still like || , it does preclude having a user-defined || extractor but I don’t think that’s problematic:

x match {
  case A || B || C =>
    ...
  case List(1 || 2, 3, 4) =>
    ...
}

And && could be used to achieve the “generalized pattern binding” proposed by @LPTK in https://lptk.github.io/programming/2018/12/12/scala-pattern-warts-improvements.html:

  case FirstPattern(a) && SecondPattern(b, c) =>  // ... use a, b, c

Which means we could eventually get rid of @ in patterns, which is just as well since @ should really be reserved for annotations.

2 Likes
#10

If alternatives must be placed in braces, commas (or even semicolons!) can still work.

x match {
  case List({1, 2}, 3, 4) => ???
  case {A, B} => ???
}

val {A, B} = x   // Though this seems an abuse

This has the advantage of matching set notation. It has the disadvantage of being an irregular form in the language.

I am not sure I support this idea, but it does seem to fulfill the requirements.

(Note: conjunctions could also be supported. e.g. case {Foo(a)} {Bar(b)} => would match only a successful match of both Foo and Bar simultaneously.)

1 Like
#11

| in patterns is perfectly fine the way it is. Changing it would be gratuitous.

There are other precedence issues in the language that are much more annoying today, and we can’t fix them because precedence is not to be changed, ever. (For example: x & 1 != 0 doesn’t parse the obvious and useful way.)

I am opposed to changing anything here.

1 Like
#12

“val (A | B) = A: Any” is "val = ". The use case for "val = " is things like “val head :: tail = list”. The use case for “(A | B)” is “x match { case (A | B) => action }”. It would unnecessarily complicate the language to make the patterns allowed for “val”, “for” and “match” distinct – the programmer would then need to know not only the pattern rules, but how they differ in each case. That’s what was meant by “edge case” – it’s a case where reasonable features are used in an unreasonable way. It’s not the sort of thing that can be helped.

1 Like