2023-03-08 [08:55:32.0902] looks like we won't make the march plenary, but we should still try to meet before yulia's gone 2023-03-10 [09:55:11.0997] Finally made the doodle: [09:55:12.0385] https://doodle.com/meeting/participate/id/dGRpPW0b [09:55:53.0983] TabAtkins: ljharb Jack Works rkirsling mpcsh rbuckton ^ 2023-03-11 [22:58:19.0602] the earliest times there are midnight for me, so I suppose that'd be doable 2023-03-14 [10:39:31.0782] Thanks, yulia! [10:39:45.0944] And yeah I keep overcommitting, I haven't been able to give this time. :( [11:14:15.0254] we can just catch up 2023-03-23 [15:30:21.0050] can we send out a gcal invite for the meeting, which i presume is on monday? if so, can we also add willian to it? 2023-03-24 [04:24:40.0268] yep [04:28:57.0119] I for some reason don [04:29:10.0044] t have everyone's emai [04:29:56.0605] in particular i don't know willian [09:46:53.0923] if you can move the event to the tc39 calendar i can add whoever's missing 2023-03-26 [23:24:23.0522] I ended up catching a cold this weekend and underprioritizing sleep is probably how it happened in the first place, so [23:25:19.0661] I think it's best for me to go to bed early and sit this one out 2023-03-27 [02:03:18.0947] moved! [02:03:49.0225] For sure, I don't think there will be anything ground moving at this meeting, just a chance to catch up and see where everyone's thoughts are at. feel better! [08:02:54.0789] something is weird with my internet [08:03:10.0519] I guess I'll be a second, this meeting requires me to be signed into Zoom [10:26:36.0157] should the time be 1 hr later? I created the meeting so i think it is on berlin time but we haven't switched to summer time yet [10:36:26.0047] also monday the 10th is easter monday [10:44:03.0391] if it's an hour later i can't do it, personally [10:44:12.0251] um what's easter monday? easter's on a sunday [10:47:19.0424] https://en.wikipedia.org/wiki/Easter_Monday -- in particular it is a holiday in canada and mgaudet (who kindly said he can attend for SM) can't come that day [10:47:27.0626] it may be a holiday in other places too [10:47:36.0237] gotcha [10:47:46.0809] (it is one in germany but that doesn't matter) [10:48:04.0833] so the time zone didn't shift the time? I wasn't sure [10:48:27.0642] > <@yulia:mozilla.org> so the time zone didn't shift the time? I wasn't sure it doesn't seem to have [10:48:32.0099] ok, great [10:48:33.0137] should we shift it to start the 2-weekly from next monday? [10:48:36.0912] that way it skips easter monday [10:48:54.0554] works for me [10:49:10.0589] (which doesn't impact anyone so i am the wrong person to answer that) [10:49:29.0557] I've invited matthew here for easier scheduling [10:49:31.0447] k, done [10:52:29.0319] (next monday i have a conflict for the first half, so i' [10:52:32.0897] * (next monday i have a conflict for the first half, so i'll show up late) [10:55:54.0848] 👋 My Mondays are relatively quiet, so should be OK. 2023-03-28 [08:33:23.0772] TabAtkins: I posted a reply to your comment on #281 (https://github.com/tc39/proposal-pattern-matching/issues/281#issuecomment-1487092535), but think it might be better to go into more detail here. I have some concerns about the the proposed grammar in your gist. Some of it seems unnecessary and it is quite heavy-handed with the use of `when`. [08:37:01.0820] JS syntax is essentially designed to be read in a way that is a rough approximation of English grammar rules, so a phrase like: ``` if match(when(pattern) = expression) { } ``` Reads like: "If match when pattern equals expression, then ..." While: ``` if (expression is pattern) { } ``` Is far easier to interpret as the English phrase "If expression is pattern, then" [08:39:11.0234] Yeah I'm drafting a response [08:40:10.0207] `match` works well as an expression form on its own since it also follows English grammar rules: ``` let x = match (expression) { when true: doA(); when false: doB(); }; ``` Which can be read as: "Let `x` [be the result of] match[ing] expression. When [`x` is] `true`, [then] `doA()`. When [`x` is] `false`, [then] `doB()`. [Otherwise, throw an error]" [08:40:23.0607] * `match` works well as an expression form on its own since it also follows English grammar rules: ``` let x = match (expression) { when true: doA(); when false: doB(); }; ``` Which can be read as: "Let `x` \[be the result of\] match\[ing\] `expression`. When \[`x` is\] `true`, \[then\] `doA()`. When \[`x` is\] `false`, \[then\] `doB()`. \[Otherwise, throw an error\]" [08:41:23.0815] My biggest concern with your proposed syntax is that it isn't very "grok"-able, which was something yulia wanted to make sure we considered. [09:04:09.0692] I've not had time to read that thread properly but I will say from a quick glance that `*if* match(*when*` did make me feel like "well that won't work" [09:04:23.0702] * I've not had time to read that thread properly but I will say from a quick glance that *if* match(*when* did make me feel like "well that won't work" [09:04:31.0300] * I've not had time to read that thread properly but I will say from a quick glance that "**if** match **when**" did make me feel like "well that won't work" [09:24:01.0029] I explored a number of novel syntax options in https://gist.github.com/rbuckton/df6ade207eecad4fc94cedc3aae79ceb, and the syntax that I favor matches the tenets I laid out in https://gist.github.com/rbuckton/fca8b4ecc4eb16422b01f2557203082b (e.g., concise, expressive, explicit, extensible, and exhaustive). I proposed `is` expressions because they are concise and compose well with existing syntactic constructs like `if`, `do-while`, `while`, `for`, conditional expressions, `yield`, `await`, arrow functions, and logical operations. I like `match` expressions because they push developers towards exhaustive matching, and are an improvement both over `switch` (with all of its failings), and conditional expressions (as `match` removes the need to cache the head expression). I very much favor minimal, yet expressive pattern syntax, such as: - Literal constant patterns (i.e., `0`, `null`, `"hello"`, etc.) - Object and array patterns (i.e., `{ x: 10, y: 20 }`, `[10, 20]`, etc.) - Qualified name patterns for custom matchers (i.e., `String`, `Point`, `Option.Some`, etc.) - Extractors for nested matching in custom matchers (i.e., `Point({ x: 10, y: 20 })`, `Foo(1, 2)`, etc.) - Negation patterns (i.e., `not null`, `not String`, etc.) - Disjunction patterns (i.e., `String or Number`, etc.) - Conjunction patterns (i.e., `Array and { length: 10 }`, etc.) - `let`/`const` patterns (i.e., `Option.Some(let value)`, `{ x: 10, y: let y }`, etc.) I favor `let`/`const` patterns because they are explicit, recognizable, and familiar. They are also bound "in place", which avoids repetition. They also remove the need for `${}` placeholders that are necessary in the current syntax only to differentiate between _references_ to existing variables and _bindings_ produced by the pattern. I generally favor `let`/`const` over Rust's `@` syntax for bindings, as `@` is very confusing in Rust, and would overload the meaning. I find keyword-based syntax to be far easier to introduce over sigil-based syntax due to the limited budget we have remaining in the language for sigils. [09:25:44.0702] * I explored a number of novel syntax options in https://gist.github.com/rbuckton/df6ade207eecad4fc94cedc3aae79ceb, and the syntax that I favor matches the tenets I laid out in https://gist.github.com/rbuckton/fca8b4ecc4eb16422b01f2557203082b (e.g., concise, expressive, explicit, extensible, and exhaustive). I proposed `is` expressions because they are concise and compose well with existing syntactic constructs like `if`, `do-while`, `while`, `for`, conditional expressions, `yield`, `await`, arrow functions, and logical operations. I like `match` expressions because they push developers towards exhaustive matching, and are an improvement both over `switch` (with all of its failings), and conditional expressions (as `match` removes the need to cache the head expression). I very much favor minimal, yet expressive pattern syntax, such as: - Literal constant patterns (i.e., `0`, `null`, `"hello"`, etc.) - Object and array patterns (i.e., `{ x: 10, y: 20 }`, `[10, 20]`, etc.) - Qualified name patterns for custom matchers (i.e., `String`, `Point`, `Option.Some`, etc.) - Extractors for nested matching in custom matchers (i.e., `Point({ x: 10, y: 20 })`, `Foo(1, 2)`, etc.) - Negation patterns (i.e., `not null`, `not String`, etc.) - Disjunction patterns (i.e., `String or Number`, etc.) - Conjunction patterns (i.e., `Array and { length: 10 }`, etc.) - `let`/`const` patterns (i.e., `Option.Some(let value)`, `{ x: 10, y: let y }`, etc.) I favor `let`/`const` patterns because they are explicit, recognizable, and familiar. They are also bound "in place", which avoids repetition. They also remove the need for `${}` placeholders that are necessary in the current syntax only to differentiate between _references_ to existing variables and _bindings_ produced by the pattern. I generally favor `let`/`const` over Rust's `@` syntax for bindings, as `@` is very confusing in Rust, and would overload the meaning of `@` in some contexts (i.e., decorators). I find keyword-based syntax to be far easier to introduce over sigil-based syntax due to the limited budget we have remaining in the language for sigils. [09:27:27.0563] Also, while I don't consider these to be necessary for an MVP pattern matching proposal, I think there is room to expand (in another "layer" or later proposal) the pattern syntax to include things like RegExp literal patterns and relational patterns. [09:28:29.0637] * Also, while I don't consider these to be necessary for an MVP pattern matching proposal, I think there is room to expand the pattern syntax in another "layer" or later proposal to include things like RegExp literal patterns and relational patterns. [09:40:34.0606] Even if my suggestions for syntax were adopted, there are still issues to consider. Should object patterns be exhaustive by default, requiring a `...` to opt out of exhaustiveness? Or, should object patterns be non-exhaustive by default, requiring something like `{| |}` to differentiate (much like Flow's exact object types). Should object patterns test whether the current match subject is an Object? Or, should anything other than `null` and `undefined` be allowed (i.e., to match `{ length }` against a string)? In the same vein, should array patterns be exhaustive by default, requiring a `...` to opt out of exhaustiveness? Should we ensure object and array patterns are aligned in terms of exhaustiveness? [09:40:57.0871] Also, it has been suggested that custom matchers be treated as functions to be called in absence of a `Symbol.matcher` method, or as classes (and thus tested via `instanceof`). Given that there are multiple camps in JS that either favor classes vs. functions/FP, do we have a solution that doesn't ostracize a large percentage of the community? Given the semantic meaning of a statement like `x is Y`, I generally favor an approach that leverages an `instanceof`-like behavior by default, while providing an opt-in mechanism for functions. This is partly due to readability, i.e., `x is String`, `x is Point`, `x is SharedArrayBuffer`, etc., vs. `x is isString`, `x is Array.isArray`, etc. [09:53:36.0654] The custom matcher protocol is expecting a one to one operation like `Matcher = obj => MatchResult`, which aligns well with an infix operation. General purpose functions aren't always suited to be called with a single argument, and in-situ within a pattern is a terrible place to try to `.bind()` something or wrap with an arrow function to supply arguments. If the concern introducing a simple mechanism to convert an existing unary test function, we could introduce a `Matcher` class that wraps. It would be nice if we could differentiate between a `class` and a `function` (at least, a `function` that isn't intending to represent an ES5-style "class"), and then branch the default behavior on that. [11:38:28.0649] I know I just dumped a bunch of stuff in here, but I'll be on PTO for the next week and a half as I'll be moving. I'll try to respond to discussions, but my responses may be delayed. I wanted to make sure I had some of my thoughts written down before going on vacation. 2023-03-30 [13:13:38.0661] Differentiating between a `class` (rather, its constructor function) and a regular function is, as far as I can tell, not possible? Maybe there's a branding thing we can lean on that's not web-exposed [13:14:15.0247] But `x = class Foo {}` and `y = function Foo(){}` produce objects that appear to be indistinguishable on the sort of inspection you can perform in DevTools. [13:17:48.0159] I'll say that the vast majority of what you typed, rbuckton-pto, I agree with and support (most is already the proposal, a few bits are additional changes that I also agree with making). There's no need to restate most of it. I also don't think we need to relitigate a bunch of questions - array and object exhaustiveness has been well-argued in the proposal and unless you think you have a strong argument for changing the current proposal it's gonna look like it currently does. [13:18:49.0838] As for syntax of the various matcher uses that I posted, I'm more than open to changes. I'm just trying to make sure that all the usages are reasonably consistent, and parseable without tying our hands on grammar changes in the future. [13:19:59.0164] i'll do some more fiddling in the thread [13:22:44.0085] One big issue that we're gonna run into, tho, is the collision of extractor patterns in matchers with extractor patterns in destructuring. It looks like extractors-in-destructuring end up claiming a big chunk of syntax space that would make it impossible to ever do anything else in those syntax slots without some awkward manipulations. How much are you insisting on extractors-in-destructuring if we have extractors-in-matchers, and can use matchers in `let` and function args in place of the existing destructuring? [13:46:30.0415] I won't be able to respond in detail until the middle of next week as I'm packing and moving over the next few days. [14:19:45.0234] np, i dropped more comments in the thread that'll be more useful to respond to, anyway [14:25:23.0270] I do wonder if we can handle the predicate case by just making `if()` a matcher pattern. Matches if its expr is true, establishes no bindings. You'd commonly have to write `x and if(foo(x))` or something, so you can test the value at that point, but still. [14:26:50.0863] Then we could remove the explicit `if()` clause from the match() arms. And without an `if()` in the grammar (and *with* the `:` separator that we have now), we can probably just remove the `when()` wrapper too. [14:27:22.0163] ```js match(val) { [a, b]: a+b; default: 4 // chosen by fair die roll; } ``` [14:27:37.0464] (this is parseable, since `default` is a reserved word) [14:28:17.0939] And then since `if()` is a matcher, `match(val) { if(foo == bar): baz; }` Just Works, no need for us to handle it specifically. [14:30:46.0439] `{foo: Custom([1, a])} and if(myTest(a))` would be a valid matcher if you didn't want to nest the `if` deeply, identical to today's proposal `when({foo: Custom([1, a])}) if(myTest(a))` [15:47:44.0144] C# also has headless cases: ```cs x switch { [a, b] => a+b, _ => 4 } ``` [16:14:08.0519] oof, i think the when wrapper is valuable and necessary [16:14:23.0926] having it look almost exactly like an object literal seems very hard to teach 2023-03-31 [18:26:20.0443] > <@ljharb:matrix.org> having it look almost exactly like an object literal seems very hard to teach It doesn't seem so bad to me, and there's nothing keeping us from using a different token than `:`. That said, I'm still fine with `when` as a clause indicator inside of `match`. [10:40:37.0836] rbuckton-pto: Was your suggestion for `let` with a destructure, a matcher in a trinary, and an object literal a serious suggestion? Or were you shitposting? I'm finding it hard to respond to that in text without seeming incredulous and/or rude, apologies. [10:41:36.0142] > having it look almost exactly like an object literal seems very hard to teach It doesn't, tho? If anything, it looks like a switch(). ^_^ [10:43:11.0309] But my interest really is just in making sure that matchers are used in as similar a fashion as possible across their usage sites. If match() wraps them in a when(), then everywhere else probably should as well. If everywhere else doesn't, then match() probably shouldn't either. I think this is necessary to make the proposal as a whole feel smooth and easy to digest. [10:44:18.0787] And inside of `match()` is perhaps the location *least* requiring of a special wrapper to indicate context, since it's a brand new construct which can *only* contain that one thing! So "a `when()` inside of `match()`, but naked everywhere else" is exactly backwards, I think. ^_^ [11:18:35.0620] > <@tabatkins:matrix.org> rbuckton-pto: Was your suggestion for `let` with a destructure, a matcher in a trinary, and an object literal a serious suggestion? Or were you shitposting? I'm finding it hard to respond to that in text without seeming incredulous and/or rude, apologies. Not shitposting, and not sure what seems out of place. I'm not advocating for `assert(val is Point(let x, let y))`, but that would be feasible with `is`. I don`t find `let when(pattern) = y` useful because we already have destructuring, which has parallels between `let [x] = y` and `[x] = y`. The `let when` syntax has no parallel in expression space. Extractors would also have parallel syntax in binding and assignment patterns. Matching is somewhat different, however. Matching is a conditional operation, destructuring is not. `let` is not conditional either, so `let when` feels out of place. [11:20:52.0144] What seemed out of place was just that it was *ridiculously* verbose. You have to name each binding *three times* to smuggle it out. Considering the objections to my earlier syntax suggestions based on the verbosity of an extra `match` and `when()`, it seemed somewhat contradictory to suggest such a pattern is best spelled so roundabout and verbosely. ^_^ [11:22:45.0381] Re: the parallel between expression and destructuring: Matchers also have `[]` and `{}`, tho. And I'm coming around pretty well to the extractor pattern being the primary way to do custom matchers (but still having ${} as an escape hatch, possibly), so that also matches up. [11:23:23.0049] So I'm not sure I understand - is it just that matchers can be *more powerful* than that? Even tho the common case is not, and they'll usually very closely resemble destructuring? [11:26:17.0119] The `let { x, y } =` example is verbose, yes, but uses what could otherwise be regular js in the initializer. [11:27:20.0888] I don't understand why that's a benefit here, considering we're talking about new syntax. [11:27:25.0151] 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. [11:28:05.0909] The closest you get to a useful example is the `assert` one, because you need to validate the condition was successful. [11:28:48.0167] Let's discuss that point directly, then, rather than trying to insinuate it via intentionally bad examples. ^_^ [11:29:46.0299] It's hard to have this conversation right now. My PC is disconnected for a move and my laptop is packed. I'm currently on matrix from my phone [11:30:13.0262] Not a problem. ^_^ go! be on vacation! stop responding! [11:49:21.0978] The `let { x, y }` example seems like a bad example, but it isn't far from a real world example in many pattern matching languages. In a lot of cases, you will see a value branch on different patterns using something like `match`, but may need to provide more than one result. Consider this example that uses a combination of `match`, extractors, and ADT enums to represent movement in a 2D game ```js const { dx, dy } = match (action) { when Action.Move({ direction: "north", distance: let dist }): { dx: 0, dy: dist }, when Action.Move({ direction: "south", distance: let dist }): { dx: 0, dy: -dist }, when Action.Move({ direction: "east", distance: let dist }): { dx: dist, dy: 0 }, when Action.Move({ direction: "west", distance: let dist }): { dx: -dist, dy: 0 } }; ``` This is a perfectly reasonable pattern, and exhibits the exact same behavior as my other example. [11:49:59.0526] * The `let { x, y }` example seems like a bad example, but it isn't far from a real world example in many pattern matching languages. In a lot of cases, you will see a value branch on different patterns using something like `match`, but may need to provide more than one result. Consider this example that uses a combination of `match`, extractors/ADT enums to represent movement in a 2D game ```js const { dx, dy } = match (action) { when Action.Move({ direction: "north", distance: let dist }): { dx: 0, dy: dist }, when Action.Move({ direction: "south", distance: let dist }): { dx: 0, dy: -dist }, when Action.Move({ direction: "east", distance: let dist }): { dx: dist, dy: 0 }, when Action.Move({ direction: "west", distance: let dist }): { dx: -dist, dy: 0 } }; ``` This is a perfectly reasonable pattern, and exhibits the exact same behavior as my other example. [11:52:15.0240] Which could also be composed with `is` (and something like throw expressions): ```js const { dx, dy } = action is Action.Move({ direction: "north", distance: let dist }) ? { dx: 0, dy: dist } : action is Action.Move({ direction: "south", distance: let dist }) ? { dx: 0, dy: -dist } : action is Action.Move({ direction: "east", distance: let dist }) ? { dx: dist, dy: 0 } : action is Action.Move({ direction: "west", distance: let dist }): { dx: -dist, dy: 0 } : (throw new MatchError()); ``` [11:52:38.0597] (I pulled out my laptop to write that up) [11:53:14.0254] Ok, now I'll break for a bit. I'll catch up more next week. [11:53:21.0199] * Which could also be composed with `is` (and something like throw expressions): ```js const { dx, dy } = action is Action.Move({ direction: "north", distance: let dist }) ? { dx: 0, dy: dist } : action is Action.Move({ direction: "south", distance: let dist }) ? { dx: 0, dy: -dist } : action is Action.Move({ direction: "east", distance: let dist }) ? { dx: dist, dy: 0 } : action is Action.Move({ direction: "west", distance: let dist }) ? { dx: -dist, dy: 0 } : (throw new MatchError()); ``` [12:04:01.0452] Yes, that's more reasonable when there are many branches, because you're also communicating what parts of the potentially-quite-different patterns are being condensed into the set of common bindings. That's useful to communicate in the code! [12:04:21.0109] But that's not a relevant concern when you're evaluating one single branch. [12:05:14.0302] (Also, anything to do with `let` directly impinges on how we want to handle matchers in arglists.) [12:11:48.0924] I'm not yet convinced on matchers in parameter lists. I do want extractors, though, which is another reason they are intended to work in destructuring as well. [13:06:21.0957] Okay updated proposal for "matchers everywhere" (longform in linked gist, abbreviated 1-pager in comment) https://github.com/tc39/proposal-pattern-matching/issues/281#issuecomment-1492546570