(This is about pattern alternatives, not union types !)
enum Tree:
case Wrap(t: Tree)
case Node(t1: Tree, t2: Tree)
case Leaf(value: Int)
import Tree.*
def rightmostValue(tree: Tree): Int =
tree match
case Wrap(subtree) | Node(_, subtree) => rightmostValue(subtree)
case Leaf(value) => value
The code above is unambiguous, right ?
We extract a value subtree
of type Tree
, and recurse on it
But this is not allowed:
Illegal variable subtree in pattern alternative
It is said as much in the Scala 2 spec:
“They may not bind variables other than wildcards.”
https://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#pattern-alternatives
My somewhat crude proposal is the following:
Binding identifier id
is allowed in pattern alternatives iff all the following conditions are true:
- each alternative
i
extractsid
with typeT_i
- effect:
id
has typeT = T_1 | ... | T_n
- if an alternative or one of its sub-patterns is
id: U_i
, thenT
must be=:= U_i
-
id
cannot be bound in a “pattern binder”id@Extractor(...)
The last condition could also be replaced by “if id
appears in a pattern binder, then the pattern must be the same in all aternatives”
Examples:
tree match
case Wrap(subtree) | Leaf(subtree) =>
// body where subtree has type Tree | Int
tree match
case Wrap(subtree) | Leaf(subtree: Int) =>
// error: subtree is bound to type Tree | Int, not Int
tree match
case Wrap(subtree@Wrap(_)) | Leaf(subtree) =>
// error: multi-bound variables cannot be part of pattern binders
tree match
case Wrap(subtree@Wrap(_)) | Node(_, subtree@Wrap(_)) =>
// Might be okay ?
This has the impact that P1 | P2
is not always equivalent to P2 | P1
:
tree match
case Wrap(t) | t: Tree =>
// not equivalent to
case t: Tree | Wrap(t) =>
Questions for you:
- Is this sound ?
- Does this have good UX ?
- Would this be useful ?
- Suggestions ?