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.ste...@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.go...@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 <fo...@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 >>> >>> >>> >>> >> >