2023-08-07 [08:10:38.0946] We didn't make useful quorum for the meeting, so offline ping: a couple of people wanted to take the gist back to their team to discuss before we made a final yay/nay on updating the proposal in the repo. Does anyone object to moving forward with this? 2023-08-08 [21:02:02.0099] i've been traveling and haven't really had a chance to fully review the gist; i'll be back on the 10th. maybe we could plan to move forward early next week? 2023-08-16 [15:03:47.0722] TabAtkins: gist review: - `plain-or-dotted-ident` presumably includes brackets and optional things and whatever, the same stuff a decorator allows after `@`? - for `()`, what happens if it's a function that does not have a Symbol.matcher property? - i really like the desugaring example for `is`, but that falls apart with the bindings bit, and that makes me nervous - for bindings in an `if` does it also apply to an `else if` and every other else in the chain? does the gist already incorporate all the discussion at the bottom of it? - there should not be an optin for regex named group bindings, since they're statically mentioned it's not the same as `with`. if someone doesn't want the bindings they just don't have to reference them. [15:04:21.0061] * TabAtkins: gist review: - `plain-or-dotted-ident` presumably includes brackets and optional things and whatever, the same stuff a decorator allows after `@`? - for `()`, what happens if it's a function that does not have a Symbol.matcher property? - i really like the desugaring example for `is`, but that falls apart with the bindings bit, and that makes me nervous - for bindings in an `if` does it also apply to an `else if` and every other else in the chain? does the gist already incorporate all the discussion at the bottom of it? - there should not be an optin for regex named group bindings, since they're statically mentioned it's not the same as `with`. if someone doesn't want the bindings they just don't have to reference them. I like the name `Symbol.matcher` and since nobody knows about or uses `Symbol.match` i think it'd be a shame to pick a worse name for our symbol :-( [15:04:40.0242] * TabAtkins: gist review: - `plain-or-dotted-ident` presumably includes brackets and optional things and whatever, the same stuff a decorator allows after `@`? - for `()`, what happens if it's a function that does not have a Symbol.matcher property? - i really like the desugaring example for `is`, but that falls apart with the bindings bit, and that makes me nervous - for bindings in an `if` does it also apply to an `else if` and every other else in the chain? does the gist already incorporate all the discussion at the bottom of it? - there should not be an optin for regex named group bindings, since they're statically mentioned it's not the same as `with`. if someone doesn't want the bindings they just don't have to reference them. I like the name `Symbol.matcher` and since nobody knows about or uses `Symbol.match` i think it'd be a shame to pick a worse name for our symbol :-( I very much do *not* want :"unapply" and think that would be a very confusing choice. 2023-08-17 [22:49:04.0623] based on my understanding: - yes, it's the same as what the decorator has - maybe it throws for things that don't have @@matcher - no comment - yes, and in TDZ if the match fails [09:25:52.0421] if the match fails why would you want the bindings available in the else, since they can't ever work? [13:24:19.0334] > <@ljharb:matrix.org> TabAtkins: gist review: > > - `plain-or-dotted-ident` presumably includes brackets and optional things and whatever, the same stuff a decorator allows after `@`? > - for `()`, what happens if it's a function that does not have a Symbol.matcher property? > - i really like the desugaring example for `is`, but that falls apart with the bindings bit, and that makes me nervous > - for bindings in an `if` does it also apply to an `else if` and every other else in the chain? > > does the gist already incorporate all the discussion at the bottom of it? > > - there should not be an optin for regex named group bindings, since they're statically mentioned it's not the same as `with`. if someone doesn't want the bindings they just don't have to reference them. > > I like the name `Symbol.matcher` and since nobody knows about or uses `Symbol.match` i think it'd be a shame to pick a worse name for our symbol :-( I very much do *not* want :"unapply" and think that would be a very confusing choice. Regarding `plain-or-dotted-ident` including brackets and optional things: No, it does not at present. Decorators also do not support optional chaining or brackets. Optional chaining might be feasible with decorators, but brackets are ambiguous with `ComputedPropertyName`, thus you must use parens to use them, i.e., `@(foo[bar])`. I didn't include element-access-like brackets in Extractors since there were some potential *AssignmentPattern* conflicts I was considering at the time. [13:47:01.0119] ok, as long as it matches decorators I’m sure it’s fine 2023-08-18 [08:44:40.0467] syntax is a hard thing huh 2023-08-21 [01:50:20.0670] hey friends, I am recovering from an anaphylactic reaction and will unfortunately have to miss tomorrow [08:03:38.0808] ron and i are waiting in the zoom; is anyone else coming? [08:04:50.0694] I'm not sure what the agenda is for today, but I may need to drop early as I'm driving my daughter up to college this afternoon and have to leave around noon. [08:07:12.0453] Whoops I forgot about meeting, coming in now. [08:07:53.0305] Agenda is talking over Jordan's thoughts, I believe, and anyone else who has final comments before we pull the trigger [08:10:10.0659] Christ on a cracker the auth for Mozilla's instance is annoying [08:10:48.0653] I tried logging in with GitHub now it's trapped in a "you don't have access, fuck off" loop [08:12:01.0210] oof [08:12:13.0385] i think you could just join without being logged in [08:12:35.0637] You cannot [08:12:47.0819] hm, i did [08:12:58.0864] Not from the link in the calendar, at least [08:13:44.0779] I'm almost certain you didn't, I get bugged for auth no matter what way I try to log in [08:14:03.0916] I'm using a diff computer, almost there [08:44:32.0434] Discussion notes at https://gist.github.com/tabatkins/51f35f88d7eea61d9ecbe3e82da817a5?permalink_comment_id=4667292#gistcomment-4667292 [08:44:49.0729] Summary: rough group consensus, I'll write up a PR updating the proposal this week and ping the group for review. 2023-08-22 [19:08:53.0034] I think I got really bummed out when we had a proposal going and then suddenly it was like "nope, back to the drawing board" [19:13:13.0792] being in this space has shown me how freeform imagining of the design of hypothetical JS features is stress and not joy to me [19:16:27.0680] so I've just been keeping space until there's something concrete again [21:19:21.0720] I think the current proposal is very close to the original, solving many of the problems discussed without wholly reinventing the syntax. [22:43:51.0950] cool [22:44:12.0595] looking forward to the PR 🙇 [13:00:44.0413] yeah the changes are actually quite minor overall. just rejiggering a few bits to be consistent in a different way, and consistent with some planned future features. 2023-08-29 [11:02:52.0524] I'm writing up the PR now, and I'm suddenly struck by an idea: instead of `when` as the branch introducer, should we use `is`? That makes it perfectly match up with the boolean form, just with an implicit LHS coming from the match block itself. [11:03:55.0685] that is: ```js match(foo) { is [let a]: ...; is {bar}: ...; default: ...; } ``` [11:04:12.0758] which syncs up with saying `foo is [let a]` outside of the match construct [13:03:21.0106] that's not bad, but there's a distinct difference wrt bindings [13:03:48.0733] also "is" and "default" don't pair very well imo [13:43:02.0124] I think they pair up as well as when/default or case/default? [13:43:41.0659] What's the binding difference you're referring to? In each match-statement clause the bindings are visible to just that clause. [13:43:54.0065] `x is y` doesn't produce bindings by itself [13:44:03.0904] Yeah it does. [13:45:03.0365] At least, that was the idea. And then we just have scoping rules in if/for/etc to contain those bindings appropriately. [13:45:22.0281] O.o i didn't realize that [13:45:32.0768] i thought `is` was just like `instanceof`, a boolean operator with no side effects [13:45:44.0485] i thought it was *only* when attached to a block that it produced bindings [13:46:30.0389] Well huh I suppose I didn't write that part into the proposal gist. I'm happier to *not* do that, fwiw. [13:46:46.0392] i think it would be *super* weird for a lone boolean test to produce bindings [13:46:54.0306] But that still doesn't impact the usage here - each branch is a conditional *thing* and would have the same sort of binding behavior. [13:47:02.0107] you'd be able to like ``` { x is y; } ``` [13:47:07.0109] * you'd be able to like ``` { x is y; /* bindings here */ } ``` [13:47:15.0185] and x would be y after that, yeah ^_^ [13:47:22.0952] right, that seems actively bad [13:47:40.0374] bindings in match are obviously critical, and they make sense in the specific constructs we discussed [13:48:00.0739] so having it be spelled "is" in match isn't inherently a problem, because "in match" is a separate context [13:48:16.0170] Anyway yeah I'll omit that, I haven't written the bindings section yet. But in a match(), each branch is essentially an if(), and you get bindings there, so it seems consistent. [13:48:23.0629] yeah i agree with that part [13:49:10.0194] if/default is the same amount of _objectionable_ to me as when/default, but i think when/default pairs more smoothly than is/default, and would love to explore other names (even if only within the champion group) for "default" [13:49:12.0742] * if/default is the same amount of _objectionable_ to me as when/default, but i think when/default pairs more smoothly than is/default, and would love to explore other names (even if only within the champion group) for "default" with "is" [13:49:40.0348] (separately, it'd be really cool if `x is y` and `x is not y` both worked) [13:50:01.0177] they do both work, yeah [13:50:06.0928] `not y` is a valid pattern [13:52:08.0192] oh score [13:52:40.0497] ok well that is glorious and also fits nicely with `x not instanceof y` and `x not in y` if someone were to propose those in the future :-D 2023-08-30 [17:11:40.0647] > <@ljharb:matrix.org> i think it would be *super* weird for a lone boolean test to produce bindings `is` introducing bindings is this proposals version of `if let` in Rust. It's extremely valuable in control flow and loops. [17:20:25.0543] It's more obviously useful when paired with something complex like extractors, i.e.: ```js if (x is Option.Some(let value)) { // 'value' is in scope } else { } ``` But is also useful in conditionals: ```js const res = x is Option.Some(let value) ? Option.Some(compute(value)) : Option.None; ``` Because `let` bindings are just a pattern, they're fairly flexible: ```js while (queue.shift() is { name: let jobName, arguments: let jobArgs }) { processJob(jobName, jobArgs); } ``` [18:05:49.0849] > <@ljharb:matrix.org> you'd be able to like > > ``` > { > x is y; > /* bindings here */ > } > ``` `x is y` doesn't produce a binding. `x is let y` would, but that's not the common case. [18:17:59.0730] hmm ok [18:18:12.0839] I’m fine with it working in the positive branch of ternaries fwiw [18:18:36.0840] but I’d expect the bare expression form to forbid let/const [21:00:57.0593] I'd hope we don't lose that, its one of the big reasons I suggested `is` in the first place. [21:13:05.0732] Plus there's been a long standing request from Anders Hejlsberg that JavaScript needs some way to introduce variables in-situ in expressions. He hasn't been partial to `do` expressions, so I'm hoping this would address that as well. `x is let y` could theoretically fill the same role as a `let..in` expression in other languages: ``` // PowerQuery x = let a = foo() in let b = a.b in [a, b]; // OpenSCAD x = let (a = foo()) let (b = a[0]) [a, b]; ``` vs. ``` // same thing with `let` patterns: x = foo() is let a, a.b is let b, [a, b]; ``` [21:16:03.0348] But be more expressive since you can use patterns: ``` x = foo() is let a and { b: let b } && [a, b]; ``` This is a more expressive since it validates that `a` is actually an object with a `b` property. [21:43:48.0434] why that request tho [21:43:53.0243] that seems a very strange ability to have [21:59:49.0025] Not from my perspective, but I've frequently used languages that have that capability. Even with optional chaining in the language I've regularly seen cases of ```js let temp; x = foo && (temp = bar(foo)) && temp.baz; ``` Where the variable for a partial result is hoisted out of the expression. It's also a common reason developers often reach for IIFEs. It's also brings a similar value to the table that pipelines have. [22:01:14.0999] you see code like that *regularly*? not inside vscode or the TS codebase? [22:01:38.0748] i haven't seen anyone reach for an IIFE since like 2014 or they started using modules, whichever came second [22:01:47.0302] * i haven't seen anyone reach for a sync IIFE since like 2014 or they started using modules, whichever came second [22:06:45.0152] IIFEs are still fairly common. TypeScript had to add control flow support to handle IIFEs, and we added control flow analysis long after ES2015 shipped. [22:50:00.0046] i'm very skeptical and surprised that people would still use IIFEs, especially now when bundlers are almost universal. [09:17:53.0003] I definitely think that use-case is just better done with a do-expr. Like, if pattern-matching ends up accidentally allowing it, whatever, but taking that as an explicit use-case to take is something I'd disagree with, do-exprs are absolutely the right way to do that sort of thing. [13:17:07.0136] ```js x = do { let x = foo(); let b = a.b; [a, b]; }; ``` 2023-08-31 [06:54:38.0082] I've reached out to Anders to get a better understanding of his interest and motivations in introducing variables in expressions, and will follow up when I have more information. From the short conversation we had yesterday, his primary interest has to do with functional programming. Languages like Haskell, Scheme, and ML, `let..in` expressions provide variables that are scoped to sub expressions. Python has something similar as well in the form of assignment expressions (`x := y`). C# even has this capability as well with variable patterns (much like the `let` patterns I've proposed), though it's not as ideal: https://dotnetfiddle.net/g4Zf13. An example from the TypeScript compiler that Anders mentioned is this one: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { let propType; return getTypeOfPropertyOfType(type, name) || (propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` This requires declaring a mutable variable at the statement level. He believes something like the following would be an improvement for FP-style development: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (const propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` Where the `propType` variable can be declared `const` and doesn't require lifting the declaration out of the expression. I'm still waiting on his feedback regarding `do`-expressions, but my take is that `do` doesn't make this expression any more succinct or clear, it just introduces a confusing statement scope in the middle of an expression: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || do { const propType = getApplicableIndexInfoForName(type, name)?.type; propType && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); }; } ``` While `x is let y`/`x is const y` isn't necessarily an ideal solution, it remains concise and is possibly even more powerful since it's combined with pattern matching: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (getApplicableIndexInfoForName(type, name) is { type: const propType } && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true)); } ``` [06:55:05.0617] * I've reached out to Anders to get a better understanding of his interest and motivations in introducing variables in expressions, and will follow up when I have more information. From the short conversation we had yesterday, his primary interest has to do with functional programming. In languages like Haskell, Scheme, and ML, `let..in` expressions provide variables that are scoped to sub expressions. Python has something similar as well in the form of assignment expressions (`x := y`). C# even has this capability as well with variable patterns (much like the `let` patterns I've proposed), though it's not as ideal: https://dotnetfiddle.net/g4Zf13. An example from the TypeScript compiler that Anders mentioned is this one: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { let propType; return getTypeOfPropertyOfType(type, name) || (propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` This requires declaring a mutable variable at the statement level. He believes something like the following would be an improvement for FP-style development: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (const propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` Where the `propType` variable can be declared `const` and doesn't require lifting the declaration out of the expression. I'm still waiting on his feedback regarding `do`-expressions, but my take is that `do` doesn't make this expression any more succinct or clear, it just introduces a confusing statement scope in the middle of an expression: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || do { const propType = getApplicableIndexInfoForName(type, name)?.type; propType && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); }; } ``` While `x is let y`/`x is const y` isn't necessarily an ideal solution, it remains concise and is possibly even more powerful since it's combined with pattern matching: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (getApplicableIndexInfoForName(type, name) is { type: const propType } && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true)); } ``` [07:14:36.0955] * I've reached out to Anders to get a better understanding of his interest and motivations in introducing variables in expressions, and will follow up when I have more information. From the short conversation we had yesterday, his primary interest has to do with functional programming. In languages like Haskell, Scheme, and ML, `let..in` expressions provide variables that are scoped to sub expressions. Python has something similar as well in the form of assignment expressions (`x := y`). C# even has this capability as well with variable patterns (much like the `let` patterns I've proposed), though it's not as ideal: https://dotnetfiddle.net/g4Zf13. An example from the TypeScript compiler that Anders mentioned is this one: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { let propType; return getTypeOfPropertyOfType(type, name) || (propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` This requires declaring a mutable variable at the statement level. He believes something like the following would be an improvement for FP-style development: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (const propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } ``` Where the `propType` variable can be declared `const` and doesn't require lifting the declaration out of the expression. I'm still waiting on his feedback regarding `do`-expressions, but my take is that `do` doesn't make this expression any more succinct or clear, it just introduces a confusing statement scope in the middle of an expression: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || do { const propType = getApplicableIndexInfoForName(type, name)?.type; propType && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); }; } ``` While `x is let y`/`x is const y` isn't necessarily an ideal solution, it remains concise and is possibly even more powerful since it's combined with pattern matching: ```ts function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { return getTypeOfPropertyOfType(type, name) || (getApplicableIndexInfoForName(type, name) is { type: const propType } && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true)); } ``` [07:45:17.0016] This was the feedback that Anders shared with me regarding the use of `do` expressions for cases like this: > I would also argue that `do` expressions attempt to solve a problem I don't have, i.e. full statement bodies in the middle of an expression. Once you need full statement blocks, you're probably better off not being in an expression context. In other words, the only problem `do` expressions would solve for me is that of giving names to sub-expressions, but they're so clumsy that I'd avoid them and advise my team to do so as well. [07:45:26.0488] * This was the feedback that Anders shared with me regarding the use of `do` expressions for cases like this: > I would also argue that `do` [expressions] attempt to solve a problem I don't have, i.e. full statement bodies in the middle of an expression. Once you need full statement blocks, you're probably better off not being in an expression context. In other words, the only problem `do` expressions would solve for me is that of giving names to sub-expressions, but they're so clumsy that I'd avoid them and advise my team to do so as well. [08:14:09.0927] > <@tabatkins:matrix.org> I'm writing up the PR now, and I'm suddenly struck by an idea: instead of `when` as the branch introducer, should we use `is`? That makes it perfectly match up with the boolean form, just with an implicit LHS coming from the match block itself. I'm ok with both. I don't know how native english speakers see this [08:17:07.0273] > <@tabatkins:matrix.org> Yeah it does. really? why `x is y` introduce binding? IMO for `const x = y is [let z]` it does not introduce binding (at least outside of the pattern), and in `if (y is [let z])` it introduce binding but you need a `let ` [08:31:24.0516] > <@ljharb:matrix.org> i think it would be *super* weird for a lone boolean test to produce bindings yes, but for consistency I'd expect the binding can be used in the pattern. `let isTwoItemsEqual = expr is [let a, a]` this should work (as it should work inside match expression) [08:36:24.0392] > <@rbuckton:matrix.org> Plus there's been a long standing request from Anders Hejlsberg that JavaScript needs some way to introduce variables in-situ in expressions. He hasn't been partial to `do` expressions, so I'm hoping this would address that as well. `x is let y` could theoretically fill the same role as a `let..in` expression in other languages: > > ``` > // PowerQuery > x = let a = foo() in > let b = a.b in > [a, b]; > > // OpenSCAD > x = let (a = foo()) > let (b = a[0]) > [a, b]; > ``` > > vs. > > ``` > // same thing with `let` patterns: > x = foo() is let a, > a.b is let b, > [a, b]; > ``` I like the `if let` part but I'm a little uncomfortable with binding in tenary `expr is [let a] ? a : b`. I know it's useful but it give me a feeling of non-JS [08:46:13.0826] > <@tabatkins:matrix.org> I'm writing up the PR now, and I'm suddenly struck by an idea: instead of `when` as the branch introducer, should we use `is`? That makes it perfectly match up with the boolean form, just with an implicit LHS coming from the match block itself. Honestly, I think `when` reads better, and has precedent in many other languages. If anything I still think `else` is better than `default` because its shorter and lines up visually: ``` match (foo) { when pattern1: ...; when pattern2: ...; else: ...; } ``` Also, in English, the word "when" at the start of a sentence can imply either a statement of fact, or begin a question. While "is" at the start of a sentence can only imply a question. So `match (foo) { when pattern: ... }` would generally be interpreted as: "**When** _foo_ is _pattern_ then ...", while `is` would generally be interpreted as: "If _foo_ **is** _pattern_ then ...", which is a bit of cognitive overhead, imo. That's why `is` makes sense as an infix expression form, since it has the infix position in the statement above. For example: `if (x is String) ...` reads as "If _x_ **is** a String, then ...". [08:46:16.0681] > <@tabatkins:matrix.org> I definitely think that use-case is just better done with a do-expr. Like, if pattern-matching ends up accidentally allowing it, whatever, but taking that as an explicit use-case to take is something I'd disagree with, do-exprs are absolutely the right way to do that sort of thing. I heard that the author of do-expr don't want to push it anymore (but he also won't block it if anyone want to move on). I hope we can have (1) statement have their expression version, instead of do expression (e.g. `let a = if (expr) { b } else { c }` or `let a = for (let x of y) y` produces an array) (2) not be able to break/return/continue inside an expression (do expr currently allows this) [08:52:25.0321] > <@rbuckton:matrix.org> Honestly, I think `when` reads better, and has precedent in many other languages. If anything I still think `else` is better than `default` because its shorter and lines up visually: > > ``` > match (foo) { > when pattern1: ...; > when pattern2: ...; > else: ...; > } > ``` > > Also, in English, the word "when" at the start of a sentence can imply either a statement of fact, or begin a question. While "is" at the start of a sentence can only imply a question. > > So `match (foo) { when pattern: ... }` would generally be interpreted as: "**When** _foo_ is _pattern_ then ...", while `is` would generally be interpreted as: "If _foo_ **is** _pattern_ then ...", which is a bit of cognitive overhead, imo. > > That's why `is` makes sense as an infix expression form, since it has the infix position in the statement above. For example: `if (x is String) ...` reads as "If _x_ **is** a String, then ...". the reason we choose `default` not `else` is because of we used to have `if` matchers and `else` cause serious ambiguous problem. Looks like we don't have it in the current proposal so it's ok to reconsider it. ```js // previous version match (e) { when (pattern) -> z, if (expr) -> a, else -> b // is it `else` of `if`, or `else` of `match`? } ``` [08:57:42.0679] I also spoke with Anders about using `x is let y` as a substitute for `let..in`, and he would rather use that then `do`, even if it is a slight abuse of the feature. If `match` itself is an expression, and each `when` match leg is an expression, and `when` patterns can have `let`, I'd be opposed to not having them in `is`. I think the bigger question would be around scoping. I think that any `let`/`const` should be bound to the nearest block scope, but we would need to expand what introduces a block scope somewhat. For example: ```js x is let y; y; // ok ``` Is essentially the same as `let y = x`, so it should have the same scope. Whereas, ```js if (x is let y) y; // ok y; // not defined ``` makes sense to me from a scoping perspective, thus `if` should be an implicit block scope. It isn't currently because we don't allow a lone `let` (or `const`) declaration immediately after `if`/`else`/`do`/etc. In the same vein, `while` should also be an implicit block scope. [08:58:04.0302] > <@rbuckton:matrix.org> I've reached out to Anders to get a better understanding of his interest and motivations in introducing variables in expressions, and will follow up when I have more information. From the short conversation we had yesterday, his primary interest has to do with functional programming. In languages like Haskell, Scheme, and ML, `let..in` expressions provide variables that are scoped to sub expressions. Python has something similar as well in the form of assignment expressions (`x := y`). C# even has this capability as well with variable patterns (much like the `let` patterns I've proposed), though it's not as ideal: https://dotnetfiddle.net/g4Zf13. > > An example from the TypeScript compiler that Anders mentioned is this one: > > ```ts > function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { > let propType; > return getTypeOfPropertyOfType(type, name) || > (propType = getApplicableIndexInfoForName(type, name)?.type) && > addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); > } > ``` > > This requires declaring a mutable variable at the statement level. He believes something like the following would be an improvement for FP-style development: > > ```ts > function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { > return getTypeOfPropertyOfType(type, name) || > (const propType = getApplicableIndexInfoForName(type, name)?.type) && > addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); > } > ``` > > Where the `propType` variable can be declared `const` and doesn't require lifting the declaration out of the expression. > > I'm still waiting on his feedback regarding `do`-expressions, but my take is that `do` doesn't make this expression any more succinct or clear, it just introduces a confusing statement scope in the middle of an expression: > > ```ts > function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { > return getTypeOfPropertyOfType(type, name) || > do { > const propType = getApplicableIndexInfoForName(type, name)?.type; > propType && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); > }; > } > ``` > > While `x is let y`/`x is const y` isn't necessarily an ideal solution, it remains concise and is possibly even more powerful since it's combined with pattern matching: > > ```ts > function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { > return getTypeOfPropertyOfType(type, name) || > (getApplicableIndexInfoForName(type, name) is { type: const propType } && > addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true)); > } > ``` Oh I really miss the time I was learning ML. [08:59:51.0814] And for scoping, `let` patterns should be visible in the `else` branch to support cases like: ``` if (x is not Foo and let y) { y; // error due to TDZ: 'y' wasn't initialized } else { y; // ok } y; // error due to scoping: 'y' isn't declared in this scope ``` [09:00:42.0677] > <@rbuckton:matrix.org> I also spoke with Anders about using `x is let y` as a substitute for `let..in`, and he would rather use that then `do`, even if it is a slight abuse of the feature. If `match` itself is an expression, and each `when` match leg is an expression, and `when` patterns can have `let`, I'd be opposed to not having them in `is`. > > I think the bigger question would be around scoping. I think that any `let`/`const` should be bound to the nearest block scope, but we would need to expand what introduces a block scope somewhat. For example: > > ```js > x is let y; > y; // ok > ``` > > Is essentially the same as `let y = x`, so it should have the same scope. > > Whereas, > > ```js > if (x is let y) y; // ok > y; // not defined > ``` > > makes sense to me from a scoping perspective, thus `if` should be an implicit block scope. It isn't currently because we don't allow a lone `let` (or `const`) declaration immediately after `if`/`else`/`do`/etc. In the same vein, `while` should also be an implicit block scope. Yes you clearly statement what I though of. We teach people `let` and `const` is "block" scoped, so `a is [let b] ? b : c` really breaks that expectation. [09:15:11.0559] How does that break that expectation? [09:36:29.0626] +1 for when/else :-p [09:45:33.0775] absolutely "implicit one-statement scope for braceless `if` blocks" is something we need, regardless of whether we create bindings in lone expressions or not