Some notes on pattern matching

When I first designed Language 84, I included a traditional pattern matching construct that looks like this:

Match numbers {
| 'cons.{n _} n
| 'nil 0
}

That expression evaluates to 0 if the numbers list is empty and evaluates to the first number in the list if numbers is not empty.

More recently, I added an alternative way to use pattern matching. It looks like this:

If [Pattern 'cons.{n _} Matches numbers]
    n
    0

That expression does almost the same thing as the previous example.

(It's different in that it only checks for the cons case. Normally, the only other possibility is nil but the code here only distinguishes between cons and not-cons.)

It is equivalent to the following expression:

Match numbers {
| 'cons.{n _} n
| _ 0
}

(The _ is used as a match-anything, bind-no-variables pattern.)

In Language 84, [Pattern 'cons.{n _} Matches numbers] is an expression and can be used anywhere a boolean value is expected.

But as the example shows, if the expression is in a control flow predicate context, it also binds variables appearing in the pattern for use in the branch taken when the condition evaluated to True.

Something interesting happens when you start using this style with And and Or operators.

If (And [Pattern 'sym.t Matches token] (STRING.equal s t))
    [TRUE_BRANCH]
    [FALSE_BRANCH]

In that expression, the t variable is bound both in the (STRING.equal s t) expression and in the [TRUE_BRANCH] of the If conditional.

Translating (naively) to the other pattern matching style, you get the following:

Match token {
| 'sym.t
    If (STRING.equal s t)
        [TRUE_BRANCH]
        [FALSE_BRANCH]
| _
    [FALSE_BRANCH]
}

Note that [FALSE_BRANCH] appears twice. To correctly expand the new style into the old style, I'll need to introduce join points into my intermediate languages to avoid code duplication. I also have to overcome the problem that the naive expansion shown above incorrectly creates a binding for t in the first [FALSE_BRANCH], where 'sym.t matches token but (STRING.equal s t) evaluates to False.

I know that Rust has something similar in its if let construct and I did have that in mind as I worked on this feature even though I haven't used Rust except to write a couple of demo programs to explore its features.

Are you familiar with similar features in a language you use? Is it something that works well? Are there issues to watch out for? I'm curious because I haven't really used a language that works like this so I'm somewhat wary about including it in my own language design.

One thing that worries me is that it feels somehow non-compositional. If you look at the above example that starts If (And ...), you can see that the condition within the If expression affects the translation of the enclosing If expression itself.

For fun, here's a curious function definition I wrote using this new style.

Define (any items pred)
    Iterate items
        (And [Pattern 'cons.{item items} Matches items]
            (Or (pred item) (Continue items)))

20 comments · 100% upvoted · other discussions

comments

abecedarius

Are you familiar with similar features in a language you use?

http://erights.org/elang/kernel/MatchBindExpr.html

This depends on E's reference semantics -- broken references in particular.

ericbb [OP]

That does seem closely related but I wonder why E uses broken references instead of just leaving the variables out-of-scope in branches that are reached when the condition evaluates to false.

abecedarius

It's simpler, given that broken references were already in the design.