7
$\begingroup$

In some languages that support pattern matching, there is a feature that allows you to match against multiple patterns at once. This is called "or patterns" in Python 3.10. For example, you can write:

match x:
    case 0 | 1:
        print("x is 0 or 1")
    case 2:
        print("x is 2")
    case _:
        print("x is something else")

Other languages that support this feature include Mathematica, Rust, and Ocaml.

However, this feature is missing in Haskell. There isn't even a GHC extension for it. I'm wondering why this is the case.

What are the pros and cons of supporting "or patterns" in pattern matching?


Update: RubenVerg pointed out in the comments that there is a GHC proposal for this feature. But it doesn't allow you to bind variables in or patterns.

$\endgroup$
1

3 Answers 3

7
$\begingroup$

Variable binding

In languages that let you bind variables in patterns, it might complicate type checking a little, but you could probably also treat it as syntactic sugar. In Haskell, for example, suppose you had the hypothetical syntax:

data Expr = EAdd Expr Expr | EMul Expr Expr | EInt Integer

f :: Expr -> Something
f e = case e of
        EAdd e1 e2 | EMul e1 e2 -> do_something_with e1 e2
        EInt i -> do_something_else_with i

You would need to ensure that e1 and e2 were bound in both patterns and had the same type. There is some design space to explore here; for example, is it okay to bind a variable in one case but not the other if that variable is unused?

One possible way to desugar this:

f e = let g e1 e2 = do_something_with e1 e2
      case e of
        EAdd e1 e2 -> g e1 e2
        EMul e1 e2 -> g e1 e2
        EInt i -> do_something_else_with i

Mercury fully supports this style of pattern matching. Here is the equivalent of the above Haskell code; for those who aren't familiar with Prolog-like syntax, in a goal, , means "logical and" and ; means "logical or".

:- type expr
    ---> eadd(expr,expr)
    ;    emul(expr,expr)
    ;    eint(int).

:- pred p(expr,something).
:- mode p(in,out) is det.

p(E, Output) :-
    (
        ( E = eadd(E1,E2)
        ; E = emul(E1,E2)
        ),
        do_something(E1, E2, Output)
    ;
        E = eint(I),
        do_something_else(I, Output)
    ).

The Mercury compiler identifies this as a mode-correct and deterministic switch.

$\endgroup$
1
  • 1
    $\begingroup$ Swift does this as well -- see this example. If you change the first _ to let y, or try to add .baz(let x) to the first case, it'll complain that the variables aren't identical between patterns in the same case. $\endgroup$
    – Bbrk24
    Commented May 29, 2023 at 10:32
4
$\begingroup$

Advantages

  • Concise and readable code: With "or patterns", you can express multiple alternative patterns in a single line, making the code more compact and easier to read. It can help reduce verbosity and improve code maintainability.

  • Reduced code duplication: When you have several patterns that require the same behaviour, combining them with "or patterns" eliminates the need to duplicate the associated code.

Disadvantages

  • Complexity and potential confusion: Introducing "or patterns" adds complexity to the pattern matching system. It can make the language more difficult to understand for newcomers and increase the potential for errors or misunderstandings.

  • Ambiguity and overlapping patterns: With "or patterns", there is a possibility of creating ambiguous or overlapping patterns that can lead to unexpected behaviour or conflicts. As mentioned in the other answer, there is ambiguity with the bitwise OR operator in Python.

  • Compiler optimisations: The absence of "or patterns" allows the compiler to make certain optimisations, such as exhaustiveness checks, which can help catch incomplete pattern matches at compile-time. Supporting "or patterns" would require more sophisticated analysis and potentially impact the efficiency of these optimisations.

$\endgroup$
2
  • 2
    $\begingroup$ The absence of "or patterns" allows the compiler to make certain optimisations, such as exhaustiveness checks. How so? One "or" pattern is the same as two "non-or" patterns. $\endgroup$
    – Longinus
    Commented May 28, 2023 at 23:26
  • $\begingroup$ Ambiguity and overlapping patterns this can happen even in the absence of or-patterns. $\endgroup$
    – Longinus
    Commented May 28, 2023 at 23:49
3
$\begingroup$

Ambiguity

At least in Python's selected syntax for this, there's semantic overload on whether you're allowing both 0 and 1 to enter the case, or if you're trying to match on the result of (0 | 1) (bitwise OR).

$\endgroup$
7
  • 1
    $\begingroup$ If the language allows (0 | 1) to be a pattern which invokes an infix operator, I'd argue it's already failed. Expressions shouldn't be in pattern position, only constants and bound variables should. Trying to overlap expressions and patterns is just a recipe for disaster if you ask me. $\endgroup$ Commented May 28, 2023 at 23:48
  • $\begingroup$ @SilvioMayolo How would you match over dependent sequences like $x :: #(x × 2) without expressions? $\endgroup$
    – Longinus
    Commented May 28, 2023 at 23:54
  • 2
    $\begingroup$ @SilvioMayolo I disagree in part. You could match on 0x0001, 0x0002, 0x0004, 0x0008 etc, or you could match on (1 << 0), (1 << 1), (1 << 2), etc. There are good arguments for allowing matching on constant expressions, but perhaps not in a dynamic language like Python. $\endgroup$
    – Pseudonym
    Commented May 29, 2023 at 0:22
  • 2
    $\begingroup$ @SilvioMayolo Egison is the first that comes to mind. $\endgroup$
    – Longinus
    Commented May 29, 2023 at 0:54
  • 1
    $\begingroup$ Swift avoids this by making its "or" patterns use commas instead of pipes -- so you'd have case 0, 1: in this case. $\endgroup$
    – Bbrk24
    Commented May 29, 2023 at 1:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .