Closed codehag closed 6 months ago
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?
I described the behavior in https://github.com/tc39/proposal-pattern-matching/issues/281#issuecomment-1486016245 (and alluded to it https://github.com/tc39/proposal-pattern-matching/issues/281#issuecomment-1490976620 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.)
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 )
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:
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.
let x = match(val) {
when <matcher>: <return-val>;
when <matcher>: <return-val>;
default: <return-val>;
};
<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:
let when <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.
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()
, and catch()
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 with when [_, first, second]
, or named groups with when {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 the foo(<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.Why would custom matchers be required to return their values as an iterator? That sounds very confusing.
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.)
Maybe I'm confused. Why would Foo()
matching syntax ever invoke the iterator protocol? (separate from user code doing so, ofc)
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 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.
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?".
Maybe I'm confused. Why would
Foo()
matching syntax ever invoke the iterator protocol? (separate from user code doing so, ofc)
For code
let Foo(a, b) = expr
Foo[@@unapply](expr)
will return an array [aVal, bVal]
, then destructing is happening (a, b)
therefore @@iterator
is called
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
and here, is a suplimentary document. https://docs.google.com/document/d/1dVaSGokKneIT3eDM41Uk67SyWtuLlTWcaJvOxsBX2i0/edit
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.