A pattern guard proposal Daphne Preston-Kendal (02 Sep 2025 11:51 UTC)
Re: A pattern guard proposal John Cowan (05 Sep 2025 20:12 UTC)
Re: A pattern guard proposal Daphne Preston-Kendal (26 Sep 2025 11:39 UTC)

Re: A pattern guard proposal Daphne Preston-Kendal 26 Sep 2025 11:39 UTC

On 2 Sep 2025, at 13:51, Daphne Preston-Kendal <xxxxxx@nonceword.org> wrote:

> If SRFI 262 is going to support pattern guards, I think SuccessorML’s model is the best to emulate – with a slight twist to allow custom pattern syntax to take advantage of it.
>
> The syntax of match clauses will change to be one of:
>
> (<<pattern>> <<body>>) as now, or
> (<<pattern>> <<guard clause>> <<body>>)
[snip]
> <<Guard clause>> must be one of the new pieces of auxiliary syntax:
[snip]
> To enable pattern syntax to make use of this, guard-with and guard-if are also patterns themselves:

On further thought, I still don’t like this.

I don’t like that I had to introduce both new auxiliary syntax outside of patterns themselves to do it. I really don’t like that there are two different ways to do the same thing with guards (wrap the whole pattern in a guard-with/guard-if or add a guard-with/guard-if), and I especially don’t like that they have the same name (okay, that could be bikeshedded) but different syntax (inherent to this design).

The reason I designed it this way was to fulfil two goals:
1. the auxiliary syntax version of guards was made to make it possible to guard variables from different values in match-values and different arguments in match-lambda
2. the pattern version of guards was made to make it possible for custom pattern syntax to take advantage of guards within its own definition

But these two requirements ended up duplicating functionality, rather than creating a single form which fulfilled both nicely.

I want to note the way it really should be done for maximum learnability and aesthetics. It’s great for equational reasoning. Unfortunately, thinking about how to formalize the semantics of this way for all cases, and implement it, makes me dizzy:

Ditch match-values, match-let-values, match-define-values, etc. Instead provide a ‘values’ pattern, so (match-values expr ((a b) …) ((c d) …)) becomes (match expr ((values a b) …) ((values c d) …)).

Then you can provide ‘guard-with’ and ‘guard-if’ as patterns only:
(match expr ((guard-if (values a b) (equal? a b)) …)) is the guard-based substitute for the non-linear pattern (values a a), or in Racket (match-values ((a a) …)).

The problem is that this means that *in general* a single pattern might want to receive one value or multiple values. At the moment there’s a safe assumption that one pattern = one value. What does ‘values’ mean in a subpattern? Can it ever be sensible? I think yes, I could rewire ‘apply’ to only ever have one subpattern and make that use a ‘values’ subpattern … but what about if someone writes something obviously nonsensical like (cons (values a b) c)? Or (values (values a b) c)? What’s the composition behaviour of ‘values’ with other patterns in general?

The other problem is that this syntax doesn’t fit into how match-lambda is designed syntactically. If you wanted to guard with pattern variables from different input args there … who knows.

Credit where due: Jack Firth on the Racket Discord first said to me that ‘match’ should really work this way. Her ‘resyntax’ package for Racket uses mechanized equational reasoning to suggest refactorings of Racket code, and she noted it would be easier for her to suggest changes like this if ‘values’ were a pattern and not a special mode of ‘match’ itself.

Daphne