-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposed simplification & increased symmetry #322
Comments
Separately, I agree with @tabatkins that the |
We used to make Identifier a binding, but that approach is considered harmful. You can see history discussion at #281
I'm unsure why
It will be added by https://github.com/tc39/proposal-discard-binding. If that proposal succeeds, apparently
We haven't seriously considered this yet, but we have an editor note in the spec "It is possible to add Initializer to MatchProperty and MatchElement."
We've discussed that in the past, see #179
|
@erights i'm a bit confused about what your OP is proposing. Are you suggesting we flip back to "idenfitiers are irrefutable patterns", and use a sigil to mark bindings, as opposed to |
On Mon, Apr 8, 2024 at 8:14 PM Jack Works ***@***.***> wrote:
We used to make Identifier a binding, but that approach is considered
harmful. You can see history discussion at #281
<#281>
That is a huge thread! Where should I look? Our could you summarize?
This issue of bare identifiers is my biggest concern, so clarification
appreciated. Thanks!
|
The core problem is readability. You can see Yulia's analysis of the old syntax https://docs.google.com/document/d/1dVaSGokKneIT3eDM41Uk67SyWtuLlTWcaJvOxsBX2i0/edit |
On Mon, Apr 8, 2024 at 8:23 PM Jordan Harband ***@***.***> wrote:
@erights <https://github.com/erights> i'm a bit confused about what your
OP is proposing. Are you suggesting we flip back to "idenfitiers are
irrefutable patterns",
Yes
and use a sigil to mark bindings, as opposed to let/const?
Not to mark bindings. Bare identifiers are bindings. ===expr uses === to
mark an expression, including a normal variable name use occurrence. And
without privileging === over other relational operators.
|
If I understand properly, that was our original proposal - and yulia and the firefox team blocked it. The change to using let/const was to unblock the proposal. |
Thanks. Looked, but without having followed the history I'm having a hard time understanding this doc. Since I do understand the current proposal, which IIUC meets @codehag 's objections, could you write one of these examples using the current state of this proposal. Or any example that should illustrate the disadvantage of my suggestion. I'll then try to rewrite in terms of my suggestion and we can compare. |
Hi @codehag ! I think we have aligned notions of programmer cognitive costs, so I'm curious if we actually arrive at opposite conclusions. Do you support the current proposal over my suggestion? Could you show a maximally challenging example written in the current proposal, for me to rewrite in my suggestion? |
Regarding that document, my purpose was not to use necessarily After discussion with the champions, I was dismayed but convinced that we could reuse aliasing. Since it is being used in Extractors, if that advances I think it will be clear how we should advance here. My understanding in the calls was that the champions had their own reasons for having let/const. This was over a year ago. If it is only for my sake that we have let/const, it should be removed. It is clear that the tendency in the language goes a different way.
The proposal authors have done a great job in trying to respond to my comments. My core concern was that the proposal is too complex, which they fundamentally disagree with. This was also what my proposed breakdown tried to do. We've moved quite far since there, but my core concern still applies. I think we should start with a simpler approach. In fact, I think that we can get very far in terms of pattern matching with extractors (it fulfills my concept of a base proposal, but this is a separate topic). I am not sure how to communicate this. I support pattern matching in the language, and a lot of the work has been really great here. But my concern was not addressed. Not only for implementation concerns, but for usability and also to allow us to correct ourselves over time. The blocking concern, when I made it, was complexity -- not any particular syntax. Unfortunately, I think my attempt in being precise about my concerns resulted in the proposal becoming more complex, which wasn't my intention. Of course, I also have personal thoughts here. In particular, we should take time with the pattern matching language -- what @erights are proposing changing in the second half of your post iiuc. We have a potential to do something really great with this, I'd like to see it fully fleshed out. It could be treated like RegExp. Another area I disagreed with the champions is that this needs to be tied to a match statement. But it doesn't look like this is the direction things are going and I don't have much time now to help with the design. So I will set these thoughts aside rather than get in the way of those who are doing the work. Regarding your suggestions, I think there are potential benefits, such as a closer relationship to existing syntax, and also drawbacks, such as searchability being generally worse for operators. Again, would love to see a deep investigation of a language for structural matching, without any concerns regarding the match statement. Overall, I support simplification. |
@codehag fwiw in the current proposal the pattern DSL is not tied only to the match statement; there's also the |
Apologies! I am not up to date. I saw the proposal recently and I was still unsure about the level of complexity, but again, not planning on getting in the way here. I'd like to see a simple subset land first and build from that, but @ljharb you and I have had a call about this and we both know where we stand :). I won't rehash it. |
My sense is that destructuring + extractors is almost all of what I want from pattern matching itself. Just for pattern matching, the only deficiencies I see with destructuring + extractors alone:
How well could we write the equivalent of Other less urgent elements which I think I still favor, but probably could be talked out of. They could at least be postponed:
But, no matter whether we generalize destructuring to become useful as refutable patterns or we introduce a pattern notion distinct from destructuring, I think we will need something like the proposed match (specimen) {
patt1: action1;
patt2: action2;
default: action3;
} is much like if (specimen ~= patt1) {
action1
} else if (specimen ~= patt2) {
action2
} else {
action3
} or even, for the expression case specimen ~= patt1 ? action1
: specimen ~= patt2 ? action2
: action3 The repetition of So, I favor enhanced destructuring centered on extractors, together with some of these additional enhancements. Perhaps we don't need a distinct notion of patterns or special I would still love to examine all this against motivating challenge examples. Please someone, fire away! |
Ah. Because we cannot parameterize an extractor inline, we cannot write extractor combinators as extractors, if we want the combinator expression inline, which we do. Sigh. |
I definitely agree that object destructuring has a few mistakes in it, in particular using |
agree so far
torn. I think I disagree. But spilled milk under the bridge. |
at the least, hopefully we can agree that having "renaming properties" and "renaming imports" be different was a mistake - it consumes double the syntax space for the same conceptual operation. |
In light of the above conversation, here's a more minimal variant of my suggestion, without distinct A.1 Expressions
Where the Of the things I omitted, the ones I'm most torn about are the combinators. But we could start without these, and later consider further extending |
It's not easy for me to read spec grammar; can you provide some code examples? |
The first interesting example on this PR's front page: match (res) {
when isEmpty: ...;
when {data: [let page] }: ...;
when {data: [let frontPage, ...let pages] }: ...;
default: ...;
} rewritten res ~= Empty() ? ... // Empty is extractor
: res ~= { data: [page, ...Empty()] } ? ... // page in scope
: res ~= { data: [frontPage, ...pages] } ? ... // frontPage, pages in scope
: ... Do you have some examples you'd like to see me rewrite? Bonus points if they show off the advantages of the current proposal over my simpler suggestion ;) |
oof, nested ternaries seems like an instant nope from me. the precedence confusion those cause is a big part of the reason why most JS styleguides/linter configs ban them. |
if (res ~= Empty()) { // Empty is extractor
...
} else if (res ~= { data: [page, ...Empty()] }) {
... // page in scope
} else if (res ~= { data: [frontPage, ...pages] }) {
... // frontPage, pages in scope
} else {
...
} |
Gotcha. That has the downside of having to repeat |
Understand. Agrees these are the costs. Issue is weighing these against the costs of the rest of the complexity of the patterns proposal. |
Also, btw, |
|
To exaggerate a bit for a kind of clarity: Both the above
Truly, once formatted with Prettier, I find tail-nested |
I personally have no issue with tail-nested conditionals, but conditionals do not provide irrefutability, while |
There are quite a few things in the proposed syntax that do not have consensus amongst the champions and are included for illustrative purposes for the benefit of ongoing discussion. To me, an MVP Pattern Matching proposal includes:
I would consider the following to be nice to have additions:
I don't believe any of the following features are necessary for an MVP, though they may be nice to have. The champions have mixed opinions here:
I have a strong preference for
I believe the current syntax makes common cases very legible while explicitly calling out bindings: match (opt) {
Option.Some(let value): ...;
Option.None: ...;
}
match (command) {
[("up" or "down" or "left" or "right") and let direction, Number and let steps]: handleMove(direction, steps);
["jump", Number and let howHigh]: jump(howHigh);
} vs the match (opt) {
${Option.Some} with [value]: ...;
${Option.None}: ...;
}
match (command) {
[("up" or "down" or "left" or "right") and direction, ${Number} and steps]: handleMove(direction, steps);
["jump", ${Number} and howHigh]: jump(howHigh);
} |
Regarding
vs.
If we can use a meaningful keyword here, I'd rather do that and save sigils like
|
this is definitely untrue, as evidenced by the very high usage of "has own" packages in the ecosystem. |
We need something easily parseable to introduce the section with the new pattern matching syntax. Due to differing treatment of division vs. regular expressions, we can't use A new token such as |
@waldemarhorwat can you elaborate? why wouldn't we be able to add a new binary keyword? wouldn't the required whitespace disambiguate? |
It would not. There are plenty of existing contexts where an expression can be followed by an identifier, in which case it would become impossible to lex any farther. |
can you give me an example? |
Here's a trivial example: One of the proposals introduces a new cover grammar that allows |
Then that sounds like an argument against that proposal, because killing new binary keywords shouldn't be considered viable. Is there an example that's actually in the language? |
Yes, there are numerous similar examples in the language. Due to past syntax decisions, the ship for introducing new binary keywords without them being reserved words has sailed. |
I'm sorry to keep repeating this, but can you share one of these examples? |
The thing that really worries me is this rule: CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] [no LineTerminator here] { MatchExpressionClauses[?Yield, ?Await] ; } This will work once, but the juxtaposition of the cover grammar with the match syntax will preclude all future attempts to introduce new syntax consisting of the form keyword (expr) {block}. We've gotten ourselves into a corner. |
The current proposal's spec text can be adapted any way needed. I agree that we need to ensure that that form can be added in the future, as well as that new unreserved binary keywords can be added, and any proposal that would disrupt that should not advance until the problem has been resolved. |
@ljharb: Example: |
It sounds like we would need to rethink some of the cover grammars, not abandon infix keywords. |
We can have either prefix non-reserved keywords or infix non-reserved keywords. They're generally mutually exclusive. Given a blank slate, we could have decided on one or the other, but we don't have a blank slate. We already have prefix non-reserved keywords to introduce various things in the language and some more in proposals in the pipeline. Trying to introduce infix non-reserved keywords would create problems for prefix non-reserved keywords. |
Proposals in the pipeline. pre-stage 3, can (and should) be changed if they're closing off design space. What are the ones in the language? (you mentioned let, so presumably var and const, but i'm not sure why that precludes new keywords?) |
Proposals closing off the design space include things like explicit resource management, which is in stage 3, as well as a bunch of things already in the language. But the patterns proposal's current form would close off more of the design space than any of those — see my comment about the match syntax above. If we're worried about closing off the design space (and I am), we should fix the syntax of the patterns proposal to not use keywords usable as identifiers to mark patterns. Things like |
Can you provide a list of the things already in the language? |
Making that list would be an important effort. Finding them all would require computer validation of the grammar, including cover grammars. I wrote such a thing for an earlier version of ECMAScript, but it would take me a while to bring it up to date. |
@ljharb I can't remember who said that, but it is said destructuring is assignment but |
FWIW, if destructuring used const sayHello = ({
to as name: string
}) => { ... } instead of: const sayHello = ({
to: name
}: {
to: string
}) => { ... } But of course it's too late. :/ |
I agree that it would be better to restore the symmetry with destructuring with respect to when/how bindings are introduced. |
Pattern matching cannot have symmetry with all existing syntax in the language, and already breaks symmetry with destructuring semantics due to array exhaustiveness. Introducing symmetry with bindings breaks symmetry with identifier references and makes the syntax even more complicated. We introduced We must break symmetry somewhere, and breaking symmetry with bindings addressed other concerns. |
Regarding If we were to handle this by punctuation, it might be worth considering something more distinctive that in particular does not break the pattern where |
Playing a little fast and loose with the metagrammar, I think this proposal should be simplified to
A.1 Expressions
PrimaryExpr:
RelationalExpr: // unchanged, but useful below
<
|<=
|>=
|>
) ShiftExprinstanceof
|in
) ShiftExprEqualityExpr:
==
|!=
|===
|!==
) RelationalExpr~=
MatchPattern // like the proposedis
MatchPattern:
void
// I'm a fan, and allows...void
=
AssignmentExpr // can we make defaults this simple?[
(MatchPattern,
)* (...
MatchPattern)?]
{
(Prop:
MatchPattern,
)* (...
MatchPattern)?}
and
MatchPattern // consider&&
?or
MatchPattern // consider||
?not
MatchPattern // consider!
?if
(
Expr)
<
|<=
|>=
|>
) ShiftExpr===
|!==
) RelationalExpr~=
MatchPatternLeft-Hand-Side Exprs: // enhance destructuring
void
~=
MatchPattern // refutableThe main change is how the distinction between comparing against values is expressed vs how bindings are introduced. The existing proposal treats a bare
Identifier
as a lexical reference to a variable from the enclosing scope, so it introduces bindings with alet
,const
, orvar
prefix. This is not at all like destructuring. IMO people will be confused by this asymmetry.I saw in the proposal a suggestion of a pattern to be able to start with a (normally binary) relational operator followed by an expression. This is the opportunity to restore the symmetry with destructuring! This gives a way to introduce expressions whose value can be compared, including expressions using variables from outer scopes. Any
Identifier
appearing normally in a pattern is binding occurrence, just as one expects from destructuring.The text was updated successfully, but these errors were encountered: