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
Rust's
if lethas turned out to be a bit unfortunate, because it doesn't compose elegantly. That leads to things like thematches!macro, at which point one can justif matches!(x, Some(y))instead ofif let Some(y) = x.Infix
Matcheslike you have seems like a good idea; it's been brought up for Rust as well. (Though in the other order for parsing reasons, likex is Some(y).) Though beware variable scoping from it -- where they should be in-scope and usable aren't obvious questions.WalkerCodeRanger 7 points · Nov 2019 · Azoth Language
Can you expand on what you mean when you say
if let"it doesn't compose elegantly." Or, link to something that explains it. I don't know that I have heard about those issues.scottmcmrust 12 points · Nov 2019 · 🦀
Well,
if x > 0 && let Some(y) = zorif let Some(y) = x && y.is_power_of_two()don't work naturally, which gets frustrating. (And they, in some sense, can't work, sinceif let true = y || bdoes work, and has a different parenthesization than the preceeding examples want, as it'sif let true = (y || b).)And making
letbe an expression is also awkward, sincelet x = 4;on its own is the syntax for bindings, which are supposed to be irrefutable patterns.It would have been much nicer to have a "real" expression for
x is Some(y)from the beginning.More discussion in https://github.com/rust-lang/rfcs/pull/2497
DerpDick90 3 points · Nov 2019
cough bow gaping governor whistle impossible plate payment existence crawl
This post was mass deleted and anonymized with Redact
matthieum 4 points · Nov 2019
Interesting, since the
if letsyntax is Rust was copied from Swift :pmatthieum 4 points · Nov 2019
I find the
isexpression quite elegant here: it reads "naturally" as plain English, and it is expected to have a boolean result making it composable.The compiler requires some boolean logic to be able to handle:
where
y'is always defined after evaluating the clause, but would not be if theandwas anor... however handling conjunctives/disjunctives seems relatively painless.