You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Edited Note: I've injured my wrist quite badly. Some context is missing here, it will be filled in later (in a week or so hopefully).
As mentioned on matrix -- and in past meetings of the champion group, i believe this proposal is too complex in its current form, and as a champion i have raised this and the epics concept as a way to alleviate this. When this appeared on the agenda for stage 2, i was not informed. I have rushed to put this together, it is only an idea, or a write up of one, of how this could be done: https://github.com/codehag/pattern-matching-epic
There are many forms of simplification and layering that are possible here. this is not the only one. Perhaps this is too fine grained. As i wasn't informed of this moving to stage 2 (likely because i was ill at the time), its clear that my contribution has been small so i've also stepped down as a champion.
sorry i can't say more now as its quite hard to type.
Custom matchers could never be added later if they’re not added in the first version, including the ability to modify the pass-through value, which is necessary for RegExps.
I’d love to hear more - once the ability to pass an arbitrary expression is present, we wouldn’t be able to add custom matchers to builtins, because code on the web could depend on ${Map} eg doing an === Map comparison, rather than “is this an instance of a Map”
I never think of this problem before because I'm very familiar with the status quo proposal, but once Yulia pointed this out, I agree this is a serious problem we need to reconsider.
Even with today's JavaScript, I am sometimes confused in the deconstruction alias syntax { a: b } which is the binding I can use? If we add one more overload to the { ... } it indeed will make things much harder to read.
let val when Option.isSome
At the first glance, this makes me think of @rbuckton 's unapply proposal. I'm much in favor of that form in the past, let Option(val) = expr. But after reading the whole document, I'm worried about if unapply syntax could cover all the abilities in Yulia's document.
Here is a feedback from a normal JavaScript developer:
I don't know too much about those concern (test & assignment, ...), but as a normal user, except isOk and maybeRetry.bind(this). I agree Yulia's version is much more readable.
This is really cool! I think challenging ourselves to reuse existing structures is an extremely worthwhile reframing of things.
In general, the layered proposal idea sounds like a fantastic antidote toward biting off too much unnecessarily. (We should definitely bikeshed a name other than "epic", as that sounds like JIRA and makes me want to quit tech forever 😄 but I think something like "layered proposal" would be descriptive.)
A few specific comments:
I adore the let ... when idea. when ... with made me feel like "oh no, there's a with now? do we really need this too?", but this reveals that my apprehension was not about functionality but rather presentation—we were just racking up too many brand-new constructs at once.
I agree with you in having no specific desire to "kill switch"—expanding it would seem okay to me, if we had a plan for how it would interact with case. Otherwise, the key benefit to match seems to be that it would be an expression (but then there's also do { switch {} }).
Your regex example addresses a complaint I had! The named capture group vs. match result situation feels totally magical in the current README, but making { groups: ... } explicit resolves that.
As a relatively minor point, I would want to disallow = when ... though. As I'm seeing it, it shouldn't just be a standalone expression.
Thanks for doing such a thorough analysis. I hope your wrist heals quickly!
(FWIW, I also don't think you'd need to step down if it's purely due to lack of recent participation; I've been even more distant myself these past months, and it'd be a loss to not have your voice in this context.)
I have read through the proposal and I'll admit I need some time to work through the design changes and put together all of my impressions and concerns, though I'd like to list a few things I have some issues with at first glance.
I'm a bit concerned about the let ... when ... syntax based on my investigation into extractor objects as an upcoming proposal. In Scala, an extractor is part of the pattern matching mechanism, and a failure to match a pattern when initializing a variable would result in an exception, not a default:
// if customer is a match, populates 'name'// if customer is not a match, throws 'scala.MatchError'valCustomerID(name) = customer;
For the extractors proposal, I am considering the same approach. For a variable declaration, a failed match would result in an error since there is no escape hatch for a refutable match. While for match, you are able to declare alternate match legs or a default for an irrefutable match.
For the extractor objects proposal, this example would instead be something like:
constIsOk={[Symbol.matcher]: response=>response.status===200};// declarationletIsOk{ body }=response;// if not ok, throws an error// matchmatch(response){IsOk{body}: ...;// if not ok, try next leg
...
}
I'm hoping that extractors will remove some of the need for with, and will better dovetail with ADT Enums and destructuring, but I don't see that being the case with let...when....
I'm also not sure what let ... when ... gives you over let ... = ... ? ... : ... in your example:
let{ body }whenisOk(response);
...
let{ body }=isOk(response) ? response : {};
The second example already gives you more control over the non-matching case. It's also not clear how let ... when ... would choose an appropriate default value. If you write let [x, y] when f(obj) will it change the default to [] instead of {}, or will it throw because the default isn't iterable?
Custom matchers could never be added later if they’re not added in the first version
Yulia does not mean we make an MVP version (like class), it writes we should design them as many different small parts (and ship them at once so it won't be another class design) that can work well together and can work on their own.
I have the same feeling about the Pipeline proposal, which can be split into Partial Function Application (f(?, 2) returns a higher order function) and F# style pipeline. In this way, Partial Function Applications can be useful on their own, and the F# style pipeline can get rid of the topic operator.
I see the same idea in Yulia's design so I like it.
We should definitely bikeshed a name other than "epic", as that sounds like JIRA and makes me want to quit tech forever 😄
Some answers to questions, this time with a bit more detail:
Custom matchers could never be added later if they’re not added in the first version, including the ability to modify the pass-through value, which is necessary for RegExps.
and
I’d love to hear more - once the ability to pass an arbitrary expression is present, we wouldn’t be able to add custom matchers to builtins, because code on the web could depend on ${Map} eg doing an === Map comparison, rather than “is this an instance of a Map”
Jordan and I discussed this in a call -- Custom matchers can be a dependency on which pattern matching is built. This would mean that we do base matchers and custom matchers before we introduce pattern matching syntax. This was my suggestion, and my answer to how custom matchers can be handled separately. In addition, an alternative splitting is one @ljharb suggested: we can split by introducing ${Builtin} at a later point in time. I am not opinionated in how this is done.
I'm also not sure what let ... when ... gives you over let ... = ... ? ... : ...
I should have been clearer here: this isn't a perfect analogy. The only thing set here is if let ... when ... is pattern matched, then it is bound. Otherwise -- we have options in what can happen. My proposal was to be unobstructive and "pass through" an undefined value on failure. Throwing an error is another option. None of this is in stone, these are ideas.
What about cases like this:
const x = Math.floor(Math.random() * 1000);
const y when isEven(x);
What is the expected value of y when x is odd?
in my original thinking: undefined. But this is open to discussion. The goal was to get us thinking of the match clause as a base of this proposal, rather than patterns or the match statement itself. Using the match clause as the base allows us to first work with base matchers, then custom matchers, then syntax.
We should definitely bikeshed a name other than "epic", as that sounds like JIRA and makes me want to quit tech forever 😄
happy with "layered proposal", thats a good suggestion
Yulia does not mean we make an MVP version (like class), it writes we should design them as many different small parts (and ship them at once so it won't be another class design) that can work well together and can work on their own.
This is largely correct: the idea is to fix our tendency to do versioned proposals (like classes), and instead have a clear story for a more complex proposal like this one, that allows room for each piece to breath and be considered carefully. It brings necessary scrutiny to the constituent parts of the proposal. For me, this exercise was educational. It is discouraging to have it always said that this is impossible, and is likely why i didn't attempt it before. But this reaction is understandable -- we've been burnt by MVPs before. Anyway, we are trying to help eachother here, my goal is not to throw you all off track or ask that you change everything. Due to a long day at work i mixed two separate concepts -- my identification of a potential syntax issue, and the question of "how might we split this while preserving the key parts".
As for shipping: sometimes we conflate shipping with stage 3. I would say stage 3 is fine, even encouraged as it will allow us to write tests and start implementing (potentially informing the design). I would also say -- shipping the proposal as a whole or piece meal is at the discretion of the champions, and they can communicate that. we have precedent for this.
I've updated the title to hopefully be more about the reframe, rather than "simplification" -- as simplification is really about decoupling, not necessarily feature removal.
@codehag and I had a productive call last night, and I've removed pattern matching from this week's agenda now that I more fully understand both her and the SpiderMonkey team's feedback.
The tl;dr as I understood it:
the proposal would be better designed (by being less overwhelming to understand to delegates and implementors, and thus receiving more scrutiny and better feedback) if it were conceptually split into different layers, regardless of whether they ship simultaneously or not.
The champion group should reevaluate whether bindings and patterns should be conflated (while still allowing for sugar for the common case to avoid repetition) to avoid user confusion from varied approaches to reading and understanding the syntax.
@codehag personally reads the bindings as similar to destructuring assignment, which in the case of separation would mean bindings come first, before patterns; I personally read them as similar to a destructuring in a function or catch block signature, which would mean patterns come first, before bindings - while subjective matters of perspective may never be resolved, we need to discuss and explore this thoroughly to ensure we're designing the best possible language feature.
In early August, I'll reach out to champions (and @codehag) to schedule a meeting to discuss the proposal. With any luck, we'll be able to present a compelling update in September that will preemptively address this feedback, and consider if we're again ready for stage 2 advancement after processing feedback from that plenary.
Reacted by Ross Kirsling, yulia, Daniel Ehrenberg, snek, Mark Cohen, Jack Works, James DiGioia, Trey Brisbane, J. S. Choi, simone and 1 more
I'm happy about the decision to take time to discuss this proposal further.
About custom matchers coming later: I really want custom matchers to happen, but I could definitely imagine them coming post-MVP. All we'd need to do is say, if you use a primitive as a matcher, it checks by === (or similar), and if you use an object, an exception is unconditionally thrown. If someone wants to compare by ===, they can do so in an if guard. This would significantly reduce the forward compatibility risk of adding custom matchers later.
Reacted by Jordan Harband, Jack Works, James DiGioia, Trey Brisbane and simone
Dropping custom matchers from the initial iteration would be a significant reduction in scope, for sure. Sure it would make pattern matching less powerful, but it would still be extremely useful.
Notably, doing that would give @rbuckton more time to progress the Extractors proposal, which could potentially then serve as a base for custom matcher syntax (an approach that has precedent in Scala and some other languages).
for(...) if(...) {...} is currently usable syntax, capable of skipping items that don't pass the condition in the if without breaking the loop. while(<condition>) breaks the loop, and for(...; <condition>; ...) breaks the loop as well.
Is for(let x of items if <test>) supposed to break the loop or skip the item?
Just carrying some conversation over from the chat room, because Ron needs to be on vacation and stop responding but this is good to store for later response:
The let { x, y } = example is verbose, yes, but uses what could otherwise be regular js in the initializer.
In a way it's meant to illustrate that a let statement isn't a good fit for pattern matching itself, because matching is conditional and let is not.
The closest you get to a useful example is the assert one, because you need to validate the condition was successful.
This is simply wrong - let is not unconditional. (Or at least, let with destructuring isn't.) let {foo} = undefined; throws a runtime error! let {foo} = {} executes successfully (binding undefined), but let {foo: {bar}} = {}; again throws. The destructuring behavior matches what you'd get if you instead wrote a series of assignments, each digging into the value with an appropriate dotted/bracketed path, and that can def fail.
Matchers can just fail a little earlier - let when {foo} = {}; would throw because the match failed. But I don't think that's a difference that makes a difference here.
Since the immediate reaction to my suggested syntax changes a few days ago was dislike of the increased verbosity, here's a new draft.
Quick summary:
Matchers are identical to what's in the repo today, except I added "predicate matchers" (if(<boolean-expr>)) and extractor matchers (foo(...) and foo{...}, which are shorthands for ${foo} with [...] and ${foo} with {...}).
Slightly simplified match() - it still uses a when prefix on each arm, but it's just a keyword rather than a wrapper, to be consistent with the additional stuff. Also dropped the if() part, since predicate matchers exist now.
letx=match(val){when<matcher>: <return-val>;
when <matcher>: <return-val>;
default: <return-val>;
};
Added <val> is <matcher> binary operator. Evaluates to true/false based on value matching the matcher.
Extended var/let/const, if(), for(), while(), catch(), and function args with matchers, using a much lighterweight syntax:
letwhen<matcher> = x; // throws if match fails, otherwise exposes bindings.
// bindings use the specified semantic (var, let, or const).
// In all other locations they use "let" semantics.
if(when <matcher> = x) {...} // executes body if match succeeds, with bindings.
for(when <matcher> of x) {...} // executes body if match succeeds, with bindings.
while(when <matcher> = x) {...} // executes body while match succeeds (breaks when it fails)
catch(when <matcher>) {...} // executes body if catch succeeds, goes to next catch() if not
// re-throws if all catches fail
function foo(when <matcher>, x when <matcher>, y = default when <matcher>) {// `when <matcher>`: throws if matcher fails, exposes bindings to body// `x when <matcher>`: throws if matcher fails, exposes bindings to body, also binds arg to x// `y = default when <matcher>`: exposed bindings to body, also binds arg to y. // Doesn't throw on failure, just binds y to default instead.// `x` and `y` here can be destructuring patterns, as usual for arguments.}
The function arg syntax is the one I'm still most unsure about, it could probably still use some fiddling for maximum readability. Possibly the is operator could be fiddled with more, too - not married to the name, and the argument order is opposite how matchers are used elsewhere.
The rest, tho, are I think minimal, clear, and importantly, very consistent and predictable.
defined how matchers can be used in destructuring patterns, as suggested by Jordan.
this replaces the special handling of let/const/var, for(), and function args - their behavior falls out of the fact that they allow destructuring. (if(), while(), and catch() still require some special handling.)
added <ident> when <matcher> to explicitly handle "name this chunk and test it more", in a consistent way with the destructuring syntax
switched regex matchers to /foo/ when <matcher> for consistency, and dropped the named-capture-groups bindings for simplicity. (You can get standard capture group with when [_, first, second], or named groups with when {groups: {groupname}}
Separated ${} syntax syntactically into "test against a variable/dynamic value" (written as ${...}) and "invoke a custom matcher" (written as ${...}(<matchers>), and interpreted identically to the foo(<matcher>) syntax). Now custom matchers are required to return their values as an iterator.
As part of this, removed ${...} with <matcher> entirely. It's no longer needed.
Defined that foo() and ${foo}() (empty arglists) don't check the custom matcher's result value, just whether it succeeded or failed.
Foo(a, b) matches the result of invoking Foo[Symbol.matcher] against [a, b]. If we match up interpolated matchers as ${Foo}(a, b), as I suggest above, then that's also matching the result against [a, b].
The return value can be an array or something; it just needs to be iterable.
(A benefit to doing this, besides the confluence in syntax, is that we no longer need the special "result object". If you return an iterable, that's a successful match; we can also let you return true/false to indicate a successful or failed match. Any other value would be a runtime error.)
That's intrinsic to the syntax? It's always been the case, both in the Extractors proposal, explicitly, and in all of my adaptations of extractors into matchers. (Foo(a, b) has always been equivalent to ${Foo} with [a,b].) I'm not sure how you think when Foo(a, b) would work, otherwise.
I hadn't realized that implication, and to me that's a very very strong argument against considering that to be the desugaring. Forcing the iterator protocol when it's not absolutely necessary seems like a very unwise idea.
I would expect it to only ever accept one argument, and have that be the pattern tested - so that if you wanted array iterator syntax, you'd type that.
That loses the very nice syntax mirroring of function-call/construction <=> matcher pattern, which we have with array and object literals. Again, this sort of matcher syntax is accepted by many existing langs in their matcher patterns, for this reason - that syntax mirroring is pretty attractive.
For example, given a Point object with a constructor like new Point(x, y), giving it a custom matcher that returns the x and y values, and is usable like when Point(x, y): Math.hypot(x, y); is pretty sweet.
Forcing that to be when Point([x, y]) is a lot less attractive, and brings up reasonable questions like "well what're the parens for, then?".
Activity
ljharb commentedon Jul 18, 2022
Custom matchers could never be added later if they’re not added in the first version, including the ability to modify the pass-through value, which is necessary for RegExps.
codehag commentedon Jul 18, 2022
thats not true.
ljharb commentedon Jul 18, 2022
I’d love to hear more - once the ability to pass an arbitrary expression is present, we wouldn’t be able to add custom matchers to builtins, because code on the web could depend on
${Map}eg doing an === Map comparison, rather than “is this an instance of a Map”Jack-Works commentedon Jul 18, 2022
I'm gonna paste my reply on Matrix.
I never think of this problem before because I'm very familiar with the status quo proposal, but once Yulia pointed this out, I agree this is a serious problem we need to reconsider.
Even with today's JavaScript, I am sometimes confused in the deconstruction alias syntax
{ a: b }which is the binding I can use? If we add one more overload to the{ ... }it indeed will make things much harder to read.At the first glance, this makes me think of @rbuckton 's unapply proposal. I'm much in favor of that form in the past,
let Option(val) = expr. But after reading the whole document, I'm worried about if unapply syntax could cover all the abilities in Yulia's document.Here is a feedback from a normal JavaScript developer:
rkirsling commentedon Jul 18, 2022
This is really cool! I think challenging ourselves to reuse existing structures is an extremely worthwhile reframing of things.
In general, the layered proposal idea sounds like a fantastic antidote toward biting off too much unnecessarily. (We should definitely bikeshed a name other than "epic", as that sounds like JIRA and makes me want to quit tech forever 😄 but I think something like "layered proposal" would be descriptive.)
A few specific comments:
I adore the
let ... whenidea.when ... withmade me feel like "oh no, there's awithnow? do we really need this too?", but this reveals that my apprehension was not about functionality but rather presentation—we were just racking up too many brand-new constructs at once.I agree with you in having no specific desire to "kill
switch"—expanding it would seem okay to me, if we had a plan for how it would interact withcase. Otherwise, the key benefit tomatchseems to be that it would be an expression (but then there's alsodo { switch {} }).Your regex example addresses a complaint I had! The named capture group vs. match result situation feels totally magical in the current README, but making
{ groups: ... }explicit resolves that.As a relatively minor point, I would want to disallow
= when ...though. As I'm seeing it, it shouldn't just be a standalone expression.Thanks for doing such a thorough analysis. I hope your wrist heals quickly!
(FWIW, I also don't think you'd need to step down if it's purely due to lack of recent participation; I've been even more distant myself these past months, and it'd be a loss to not have your voice in this context.)
rbuckton commentedon Jul 18, 2022
I have read through the proposal and I'll admit I need some time to work through the design changes and put together all of my impressions and concerns, though I'd like to list a few things I have some issues with at first glance.
I'm a bit concerned about the
let ... when ...syntax based on my investigation into extractor objects as an upcoming proposal. In Scala, an extractor is part of the pattern matching mechanism, and a failure to match a pattern when initializing a variable would result in an exception, not a default:For the extractors proposal, I am considering the same approach. For a variable declaration, a failed match would result in an error since there is no escape hatch for a refutable match. While for
match, you are able to declare alternate match legs or adefaultfor an irrefutable match.For the extractor objects proposal, this example would instead be something like:
I'm hoping that extractors will remove some of the need for
with, and will better dovetail with ADT Enums and destructuring, but I don't see that being the case withlet...when....I'm also not sure what
let ... when ...gives you overlet ... = ... ? ... : ...in your example:The second example already gives you more control over the non-matching case. It's also not clear how
let ... when ...would choose an appropriate default value. If you writelet [x, y] when f(obj)will it change the default to[]instead of{}, or will it throw because the default isn't iterable?What about cases like this:
What is the expected value of
ywhenxis odd?Please, take whatever time you need to respond.
Jack-Works commentedon Jul 19, 2022
Yulia does not mean we make an MVP version (like class), it writes we should design them as many different small parts (and ship them at once so it won't be another
classdesign) that can work well together and can work on their own.I have the same feeling about the Pipeline proposal, which can be split into Partial Function Application (
f(?, 2)returns a higher order function) and F# style pipeline. In this way, Partial Function Applications can be useful on their own, and the F# style pipeline can get rid of the topic operator.I see the same idea in Yulia's design so I like it.
LOL I agree
Oh No, please don't. See my reply at tc39/proposal-do-expressions#75
I noticed that there is something that @rbuckton's unapply cannot do but Yulia's version can.
This is both two designs can do, but the following is not
codehag commentedon Jul 19, 2022
Some answers to questions, this time with a bit more detail:
and
Jordan and I discussed this in a call -- Custom matchers can be a dependency on which pattern matching is built. This would mean that we do base matchers and custom matchers before we introduce pattern matching syntax. This was my suggestion, and my answer to how custom matchers can be handled separately. In addition, an alternative splitting is one @ljharb suggested: we can split by introducing ${Builtin} at a later point in time. I am not opinionated in how this is done.
To @rbuckton
I should have been clearer here: this isn't a perfect analogy. The only thing set here is if
let ... when ...is pattern matched, then it is bound. Otherwise -- we have options in what can happen. My proposal was to be unobstructive and "pass through" an undefined value on failure. Throwing an error is another option. None of this is in stone, these are ideas.in my original thinking: undefined. But this is open to discussion. The goal was to get us thinking of the match clause as a base of this proposal, rather than patterns or the match statement itself. Using the match clause as the base allows us to first work with base matchers, then custom matchers, then syntax.
happy with "layered proposal", thats a good suggestion
This is largely correct: the idea is to fix our tendency to do versioned proposals (like classes), and instead have a clear story for a more complex proposal like this one, that allows room for each piece to breath and be considered carefully. It brings necessary scrutiny to the constituent parts of the proposal. For me, this exercise was educational. It is discouraging to have it always said that this is impossible, and is likely why i didn't attempt it before. But this reaction is understandable -- we've been burnt by MVPs before. Anyway, we are trying to help eachother here, my goal is not to throw you all off track or ask that you change everything. Due to a long day at work i mixed two separate concepts -- my identification of a potential syntax issue, and the question of "how might we split this while preserving the key parts".
As for shipping: sometimes we conflate shipping with stage 3. I would say stage 3 is fine, even encouraged as it will allow us to write tests and start implementing (potentially informing the design). I would also say -- shipping the proposal as a whole or piece meal is at the discretion of the champions, and they can communicate that. we have precedent for this.
[-]This needs to be simplified[/-][+]We should reframe the approach here[/+]codehag commentedon Jul 19, 2022
I've updated the title to hopefully be more about the reframe, rather than "simplification" -- as simplification is really about decoupling, not necessarily feature removal.
ljharb commentedon Jul 19, 2022
@codehag and I had a productive call last night, and I've removed pattern matching from this week's agenda now that I more fully understand both her and the SpiderMonkey team's feedback.
The tl;dr as I understood it:
In early August, I'll reach out to champions (and @codehag) to schedule a meeting to discuss the proposal. With any luck, we'll be able to present a compelling update in September that will preemptively address this feedback, and consider if we're again ready for stage 2 advancement after processing feedback from that plenary.
littledan commentedon Jul 19, 2022
I'm happy about the decision to take time to discuss this proposal further.
About custom matchers coming later: I really want custom matchers to happen, but I could definitely imagine them coming post-MVP. All we'd need to do is say, if you use a primitive as a matcher, it checks by
===(or similar), and if you use an object, an exception is unconditionally thrown. If someone wants to compare by===, they can do so in anifguard. This would significantly reduce the forward compatibility risk of adding custom matchers later.treybrisbane commentedon Jul 20, 2022
Dropping custom matchers from the initial iteration would be a significant reduction in scope, for sure. Sure it would make pattern matching less powerful, but it would still be extremely useful.
Notably, doing that would give @rbuckton more time to progress the Extractors proposal, which could potentially then serve as a base for custom matcher syntax (an approach that has precedent in Scala and some other languages).
38 remaining items
CadenP commentedon Mar 31, 2023
for(...) if(...) {...}is currently usable syntax, capable of skipping items that don't pass the condition in theifwithout breaking the loop.while(<condition>)breaks the loop, andfor(...; <condition>; ...)breaks the loop as well.Is
for(let x of items if <test>)supposed to break the loop or skip the item?tabatkins commentedon Mar 31, 2023
I described the behavior in #281 (comment) (and alluded to it #281 (comment) with talk of Python's list comprehensions). It would skip the item.
(I didn't grok that
for(...) if(...) {...}was already valid syntax, but you're right, it is. Phew, braceless blocks are really a trip sometimes.)tabatkins commentedon Mar 31, 2023
Just carrying some conversation over from the chat room, because Ron needs to be on vacation and stop responding but this is good to store for later response:
(from @rbuckton )
This is simply wrong -
letis not unconditional. (Or at least,letwith destructuring isn't.)let {foo} = undefined;throws a runtime error!let {foo} = {}executes successfully (binding undefined), butlet {foo: {bar}} = {};again throws. The destructuring behavior matches what you'd get if you instead wrote a series of assignments, each digging into the value with an appropriate dotted/bracketed path, and that can def fail.Matchers can just fail a little earlier -
let when {foo} = {};would throw because the match failed. But I don't think that's a difference that makes a difference here.tabatkins commentedon Mar 31, 2023
Since the immediate reaction to my suggested syntax changes a few days ago was dislike of the increased verbosity, here's a new draft.
Quick summary:
Matchers are identical to what's in the repo today, except I added "predicate matchers" (
if(<boolean-expr>)) and extractor matchers (foo(...)andfoo{...}, which are shorthands for${foo} with [...]and${foo} with {...}).Slightly simplified
match()- it still uses awhenprefix on each arm, but it's just a keyword rather than a wrapper, to be consistent with the additional stuff. Also dropped theif()part, since predicate matchers exist now.Added
<val> is <matcher>binary operator. Evaluates to true/false based on value matching the matcher.Extended var/let/const, if(), for(), while(), catch(), and function args with matchers, using a much lighterweight syntax:
The function arg syntax is the one I'm still most unsure about, it could probably still use some fiddling for maximum readability. Possibly the
isoperator could be fiddled with more, too - not married to the name, and the argument order is opposite how matchers are used elsewhere.The rest, tho, are I think minimal, clear, and importantly, very consistent and predictable.
tabatkins commentedon Apr 7, 2023
Been fiddling with my draft proposal for "matchers everywhere" more, based on feedback from several people.
Major changes from above:
let/const/var,for(), and function args - their behavior falls out of the fact that they allow destructuring. (if(),while(), andcatch()still require some special handling.)<ident> when <matcher>to explicitly handle "name this chunk and test it more", in a consistent way with the destructuring syntax/foo/ when <matcher>for consistency, and dropped the named-capture-groups bindings for simplicity. (You can get standard capture group withwhen [_, first, second], or named groups withwhen {groups: {groupname}}${}syntax syntactically into "test against a variable/dynamic value" (written as${...}) and "invoke a custom matcher" (written as${...}(<matchers>), and interpreted identically to thefoo(<matcher>)syntax). Now custom matchers are required to return their values as an iterator.${...} with <matcher>entirely. It's no longer needed.foo()and${foo}()(empty arglists) don't check the custom matcher's result value, just whether it succeeded or failed.ljharb commentedon Apr 7, 2023
Why would custom matchers be required to return their values as an iterator? That sounds very confusing.
tabatkins commentedon Apr 14, 2023
Foo(a, b)matches the result of invokingFoo[Symbol.matcher]against[a, b]. If we match up interpolated matchers as${Foo}(a, b), as I suggest above, then that's also matching the result against[a, b].The return value can be an array or something; it just needs to be iterable.
(A benefit to doing this, besides the confluence in syntax, is that we no longer need the special "result object". If you return an iterable, that's a successful match; we can also let you return true/false to indicate a successful or failed match. Any other value would be a runtime error.)
ljharb commentedon Apr 14, 2023
Maybe I'm confused. Why would
Foo()matching syntax ever invoke the iterator protocol? (separate from user code doing so, ofc)tabatkins commentedon Apr 14, 2023
That's intrinsic to the syntax? It's always been the case, both in the Extractors proposal, explicitly, and in all of my adaptations of extractors into matchers. (
Foo(a, b)has always been equivalent to${Foo} with [a,b].) I'm not sure how you thinkwhen Foo(a, b)would work, otherwise.ljharb commentedon Apr 14, 2023
I hadn't realized that implication, and to me that's a very very strong argument against considering that to be the desugaring. Forcing the iterator protocol when it's not absolutely necessary seems like a very unwise idea.
tabatkins commentedon Apr 14, 2023
I have no idea what else the desugaring could possibly be. And this exact desugaring is used by other langs already, like Python's class matchers.
ljharb commentedon Apr 14, 2023
I would expect it to only ever accept one argument, and have that be the pattern tested - so that if you wanted array iterator syntax, you'd type that.
tabatkins commentedon Apr 14, 2023
That loses the very nice syntax mirroring of function-call/construction <=> matcher pattern, which we have with array and object literals. Again, this sort of matcher syntax is accepted by many existing langs in their matcher patterns, for this reason - that syntax mirroring is pretty attractive.
For example, given a
Pointobject with a constructor likenew Point(x, y), giving it a custom matcher that returns the x and y values, and is usable likewhen Point(x, y): Math.hypot(x, y);is pretty sweet.Forcing that to be
when Point([x, y])is a lot less attractive, and brings up reasonable questions like "well what're the parens for, then?".Jack-Works commentedon Jul 20, 2023
For code
Foo[@@unapply](expr)will return an array[aVal, bVal], then destructing is happening(a, b)therefore@@iteratoris called