Skip to content
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

Drop as, rely on and #219

Closed
tabatkins opened this issue Aug 5, 2021 · 19 comments
Closed

Drop as, rely on and #219

tabatkins opened this issue Aug 5, 2021 · 19 comments
Labels
champion group discussion syntax discussion Bikeshedding about syntax, not semantics.

Comments

@tabatkins
Copy link
Collaborator

Since we're committed to having and (with some spelling), this gives us an opportunity to simplify the syntax, particularly around custom matchers.

Right now, at any given point in a pattern, you have a choice - you can either bind the value under consideration to a name with an ident matcher, or further pattern-match it with any other matcher. If you want to do both, you append the as clause to the "other" matcher, like when ([a,b,c] as list) ....

However, the and combinator lets us do this already, by passing the value to both an ident matcher and a further pattern: when (list and [a,b,c]) .... Since the ident matcher always succeeds in matching, this has the same matching behavior as just providing the second matcher, but it provides an additional binding.

This particularly lets us avoid some confusion around custom matchers, which already have their own subordinate with clause. Starting from when ^Foo with [a,b], currently you can bind the value before passing thru the custom matcher with when (^Foo with [a,b]) as preval, or the value after passing thru the custom matcher with when ^Foo with ([a,b] as postval). when ^Foo with [a,b] as mysteryval is invalid, as it's ambiguous.

Instead we could just use and to write the patterns when preval and ^Foo with [a,b] or when ^Foo with (postval and [a,b]).

That's one less syntax complexity to worry about, with zero loss of functionality and ~zero gain in verbosity. The only concern is that people might not intuitively grasp that and lets them do this sort of binding, while that's the entire point of as so it's obvious. But I suspect this is the sort of thing that will show up early in tutorials and just become a known pattern, so I'm not too concerned about it.

Thoughts?

@tabatkins tabatkins added champion group discussion syntax discussion Bikeshedding about syntax, not semantics. labels Aug 5, 2021
@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

My concern is what I've described before: without as, a destructuring match like { foo } will fail the entire match if foo is not present, when what I often want is for it to just be undefined - iow, by using as, i'm explicitly saying i don't care about the presence of the item.

@tabatkins
Copy link
Collaborator Author

Again, that's assuming that the argument to as is a destructuring pattern rather than just an ident, and I'm really opposed to that, precisely because it looks like an object pattern but is not.

@tabatkins
Copy link
Collaborator Author

Also you can already write that as when {foo=undefined}, since we're keeping object patterns a strict superset of destructuring anyway.

@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

I don't think "it looks like an object pattern but is not" is actually a problem, because it would either work the same, or syntax error, and you'd immediately learn the difference.

To your second comment tho - that's a fair point - if object/array patterns allow defaults, and defaulting them bypasses the presence check, then perhaps there's no need for as.

@tabatkins
Copy link
Collaborator Author

I don't think "it looks like an object pattern but is not" is actually a problem, because it would either work the same, or syntax error, and you'd immediately learn the difference.

We've had this discussion previously, and those aren't the only possibilities. If the matchable has a foo property, when {foo} and when _ as {foo} act identically. When it doesn't, when {foo} fails to match, while when _ as {foo} matches but binds "foo" to undefined. There's no syntax error in sight.

This is particularly confusing with custom matchers, since they have the same prelude: when ^Foo as {foo} vs when ^Foo with {foo}, and learning that one auto-defaults and allows some types of nested patterns (which also then auto-default) while the other allows all nested patterns but can fail the match, is a big potential source of confusion that I'm really strongly opposed to.

You can just destructure the value in the body. Pattern-matching should be about matching patterns.

@mpcsh
Copy link
Member

mpcsh commented Aug 5, 2021

I'm also moderately opposed to dropping as because I think teaching the and-as-as usage will be significantly harder (be it in a tutorial, running across it in a codebase, etc).

I also agree with Tab's assessment that allowing destructuring patterns after as will create confusion, and that allowing it goes against the goal of this proposal to prioritize pattern matching.

@tabatkins
Copy link
Collaborator Author

You really think teaching people foo and {pattern} is harder than {pattern} as foo?

@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

I think as without destructuring patterns is completely useless.

@tabatkins
Copy link
Collaborator Author

??? I have no idea what you mean here. The point of as is to grab a value at a particular point and further pattern-match on it. Further destructuring the value is clearly a value-add and not something people will want all or even most of the time.

@theScottyJam
Copy link
Contributor

theScottyJam commented Aug 5, 2021

Another advantage to favoring the use of foo and {pattern} over {pattern} as foo is that it enables an ordering that's easier to read. I know one of the dislikes about the as keyword is the fact that it places the binding identifier after the pattern instead of before.

Compare these:

when ({ user: { groups: [firstGroup, ...others] as groups } as users })

when ({ user: users and { groups: groups and [firstGroup, ...others] } })

I would argue that the second version is more readable. The problem with the first, is when you see something like } as users, you have to go to the other matching brace to figure out what property we're binding to the identifier "user".

To me, this means the only time I would ever use the as keyword is if I'm trying to destructure (not just bind to an identifier). And, I agree that it would be confusing to have some operators, such as with cause pattern matching to happen afterwards, while other operators, such as as to just do destructuring.

@tabatkins
Copy link
Collaborator Author

I would argue that the second version is more readable. The problem with the first, is when you see something like } as users, you have to go to the other matching brace to figure out what property we're binding to the identifier "user".

Yeah, I also think this is a pretty significant readability win.

@ljharb
Copy link
Member

ljharb commented Aug 5, 2021

@tabatkins it's something i'll want, without further pattern-matching, the great majority of the time. With defaulting, I can get it with and, though, so it's probably ok to drop as entirely for my use cases.

@theScottyJam
Copy link
Contributor

theScottyJam commented Aug 5, 2021

I'm also moderately opposed to dropping as because I think teaching the and-as-as usage will be significantly harder (be it in a tutorial, running across it in a codebase, etc).

@mpcsh - As for this point, I would argue that the learning curve is easier if we drop "as", not harder. Less operators means less to learn. Users will have to learn how to use the "and" operator anyways, and will learn that they're able to bind to identifiers using "and" just like they can do with "as".

Plus, I like to avoid feature overlap wherever it's sensible to do so. It's better to give developers clear paths instead of multiple almost-the-same options that they have to mentally bikeshed over whenever confronted with a choice (Should I use "and" or "as" here? What's the common practice out there for this scenario? Does my style guide say one thing or the other? Maybe I'll google around).

@mpcsh
Copy link
Member

mpcsh commented Aug 6, 2021

You really think teaching people foo and {pattern} is harder than {pattern} as foo?

Yes, I do. Though I think this thread has mostly run its course as a matter of opinions - I don't think we can reach any meaningful conclusion without conducting actual research on this topic.

As for this point, I would argue that the learning curve is easier if we drop "as", not harder.

We can all make perfectly cogent arguments as to why it will be easier or harder to teach and-as-as, but those arguments won't hold any weight without concrete research.

@fedeci
Copy link

fedeci commented Aug 6, 2021

What about having two proposals one with as and one without it?

@theScottyJam
Copy link
Contributor

theScottyJam commented Aug 6, 2021

I'm not sure I really understand your viewpoint @mpcsh (but maybe you could expound a bit?).

For someone learning the pattern matching syntax, how is learning both "and" and "as" operators simpler than just learning an "and" operator? Perhaps, when you say "easier to teach", you're just talking about the complexity of teaching how to bind an intermediate pattern, and aren't talking about how difficult it is to teach the entire syntax construct of pattern matching? If that's the case, then yes, I would agree that it's easier to teach how to use a dedicated binding operator to bind, over teaching the more general-purpose "and" operator (you wouldn't need to do a study to convince me of this).

If we follow @tabatkins suggestion and make "as" bindings only allow identifiers on the RHS (not destructuring patterns), then "as" would be nothing more than a strict subset of the "and" operator. It would be as if we introduced a new "<-" operator that works exactly the same as "=", except you can only destructor on the LHS. e.g.

// These two do the same thing
const { point } = data
const { point } <- data

const x = data // But "=" can also assign without destructuring

// These two do the same thing
when ({ point } and data)
when ({ point } as data)

when ({ point } and ^Thing) // But "and" can also pattern match on the RHS.

Any point we make in favor of keeping the "as" operator, would also be a point in favor of introducing this new "<-" operator. For example, I could argue that it would be slightly easier to teach destructuring with "<-" then "=", because "<-" is a dedicated destructuring operator and doesn't do anything else.


OK, maybe I'm falling a bit into the "invalid analogy" logical fallacy there. I will say that the one thing that "as" has going for it is the fact that it reads a little nicer. But I'm not sure "better wording" is enough of an argument to introduce a new special-case operator that provides no new functionality. There's struggle in trying to even get a new filter function through, that behaves almost identically as the current one, but has better wording to make it clear if you're "filtering for" or "filtering out" (See these meeting notes which shows people weren't too optimistic about the idea) - if a new function is struggling to get through that's trying to achieve this objective, I'm not too optimistic about new syntax.

@mpcsh
Copy link
Member

mpcsh commented Aug 9, 2021

My reasoning here is that I believe that top-level irrefutable matches are conceptually hard. Introducing an as keyword pushes top-level irrefutable matches into "advanced concept" territory, and I think that's a good thing.

I do think the hypothetical <- operator is an invalid analogy - as and and are not at all semantically the same, whereas <- and = are; furthermore, = is a perfectly clear name for what it does, whereas and doesn't immediately imply "re-binding" by its name.

@theScottyJam
Copy link
Contributor

While I'm still in favor with dropping "as", I'll note another point in favor of keeping it.

"and" is a pattern-matching only operator. It wouldn't make sense to allow "and" when doing destructuring.

We could provide "as" as its own proposal, which will modify both destructuring and pattern-matching, allowing you to do intermediate bindings while destructuring. I know there's many people who would love to have this kind of behavior while destructuring.

@mpcsh
Copy link
Member

mpcsh commented Dec 6, 2021

(see conclusion in #188)

@mpcsh mpcsh closed this as completed Dec 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
champion group discussion syntax discussion Bikeshedding about syntax, not semantics.
Projects
None yet
Development

No branches or pull requests

5 participants