Patterns vs guards
Gavin Bierman
gavin.bierman at oracle.com
Thu Jan 21 21:33:05 UTC 2021
Agreed. The “problem” with guards is that they are expressions that hang off on to one side. I think we can do better than that by leaning into pattern composition, and the elegant idea of a guard pattern that turns a boolean expression into a pattern.
Gavin
> On 18 Jan 2021, at 16:54, Guy Steele <guy.steele at oracle.com> wrote:
>
> I agree with this analysis, especially the comment about “the One True Source Of Expressiveness: composition”.
>
> —Guy
>
>> On Jan 18, 2021, at 10:20 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
>>
>> I think the question here is mostly one of terminology. Let’s restate the problem first. The canonical example is:
>>
>> if (p instanceof Point(var x, var y) && x == y) { /* diagonal */ }
>>
>> Here, the language is fully powerful enough for the pattern to test-and-extract on Point-ness, and the conjoined clause refines with an arbitrary predicate. This comes “for free” because expressions compose.
>>
>> The switch analogue is:
>>
>> switch (p) {
>> case Point(var x, var y):
>> // I want to do one thing for diagonal points, and something else for other points
>> }
>>
>> This is where it gets messy, because switch gives you one chance to classify the target, and you have to live with that. I think we all agree that making cases powerful enough to include type / deconstruction patterns has a “glass half empty” character to it; there will be plenty of “cases” where we can’t express what we want using a pattern switch, at least not without some ugly and error-prone contortions.
>>
>> There have been three ways proposed out of this mess:
>>
>> 1. Have a “continue” statement that lets a case body say “whoops, I fell into the wrong case, please keep trying to match at the next case.”
>> 2. Have an explicit feature of the switch statement to refine the pattern with a predicate, such as: “case P when(e)”.
>> 3. Find a way to turn boolean expressions into patterns, and just use pattern-AND composition.
>>
>> The confusion over terminology stems from the fact that we could describe all these as sorts of guards: 1 is an “imperative guard”, 2 is a “switch guard”, and 3 is a “guard pattern.” Which underscores we want some kind of guarding-feature no matter what.
>>
>> John has defended #1 as being a good “primitive”. And from a VM-design perspective, I agree; it is the sort of “control flow assembly language” we might want, and it covers 100% of the cases. Unfortunately, as a user tool, it has fewer good characteristics; it is like fallthrough squared.
>>
>> #2 had seemed like a “tolerable evil.” From a language design perspective, it is surely a “bag” of sorts; it is limited in purpose, and doesn’t compose with other features. It’s basically a “patch”, but one that users might find easy enough to reason about. It is a candidate for a “worse is better” solution.
>>
>> #3 is a feature that should make a PL designer happy; it is strictly more powerful than #2, is more broadly applicable (we can use it in other contexts where patterns are allowed), and its power derives from the One True Source Of Expressiveness: composition.
>>
>> Even adding constant patterns or relational patterns (>= 0) doesn’t really remove the need for some sort of guarding.
>>
>> The question is how we want to get there. #3 amounts to “turn a guard into a pattern”. We might call it a “guard pattern”, or a “boolean pattern”. But the terminology has gotten fuzzy, I agree.
>>
>> For the record, I am now strongly in Camp #3: we want AND pattern combination anyway, so let’s find some way to turn a boolean expression into a pattern and call that a win.
>>
>>> On Jan 18, 2021, at 3:03 AM, Remi Forax <forax at univ-mlv.fr> wrote:
>>>
>>> Hi everybody,
>>> following the discussion about && / and between guards and patterns,
>>> i don't think i've a clear understanding on what is a pattern and what is a guard.
>>>
>>> When we first discuss of guards, the separation was a kind of clean, a pattern match something and a guard provides further refinements.
>>> By example, with a made a syntax,
>>> case Point(var x, var y) && x > 0 && y > 0
>>> Point(var x, var y) is the pattern and x > 0 and y > 0 are the guards.
>>>
>>> So a pattern asks if something match and a guard uses the binding to add restrictions.
>>>
>>> As Brian said, in C#, you can directly test inside a pattern, using a weird syntax chosen by C# like >0,
>>> so the same example can be rewritten
>>> case Point(>0, >0)
>>> and with the bindings, i suppose something like
>>> case Point(var x >0, var y >0)
>>>
>>> Here the line between a pattern and a guard starts to become blurry, because >0 is pattern.
>>>
>>> So if there a difference between a pattern and a guard ?
>>>
>>> regards,
>>> Rémi
>>>
>>>
>>>
>>>
>>
>
More information about the amber-spec-observers
mailing list