00:44
<ljharb>
I will be happy to revisit it and add the omitted data properties as a proposal once the websites are fixed. I can’t speak to chromes opinion on that tho.
00:47
<bakkot>
assuming it's web-compat to do so, which it may not be
00:47
<bakkot>
whereas it would almost certainly be web-compat to change the accessors to data properties as long as the people in this room agreed not to rely on them being accessors
01:35
<rbuckton>
Do we just need to spec a special data property flag that allows assignment of the property on an instance even if the prototype is non-writable/non-configurable? It seems to me this isn't the last time this will come up.
02:13
<bakkot>
I think that would be worse than just having a bunch of accessors.
17:14
<littledan>
FYI I will miss the first 90 minutes today
17:22
<Michael Ficarra>
Rezvan: Does Chrome have a position here? If these properties are omitted for now, would you consider attempting to add data properties again in the future, or are accessors the only option?
18:02
<Chris de Almeida>
#jsx:matrix.org
18:04
<rbuckton>
I wonder if a generalized canParse solution for JS would be viable as an alternative to using eval or Function for syntactic feature testing
18:09
<Richard Gibson>
I wonder if a generalized canParse solution for JS would be viable as an alternative to using eval or Function for syntactic feature testing
definitely, and similar to CSS.supports
18:13
<bakkot>

how do people feel about

Function.prototype.try = function(...args) {
  try {
    return { success: true, value: this.apply(null, args) };
  } catch {
    return { success: false, value: undefined };
  }
}

as in

let { success, value } = JSON.parse.try(string);
18:14
<bakkot>
only works for functions not methods, I guess
18:15
<bakkot>
guess there could be a tryWithThis which would let you do JSON.parse.try(JSON, string)
18:15
<ljharb>
i like that (the non-method one), it has the side benefit that it encourages use of standalone functions
18:17
<Richard Gibson>
let { success, value } = tryApply(fn, thisArgument, argumentsList) would be language-consistent but less ergonomic
18:17
<bakkot>
fn.tryApply(thisArgument, argumentsList) is at least as consistent, surely
18:18
<ljharb>
i'd swap those two but sure (thisArg goes last, just like all the array methods, since it's more optional than args)
18:18
<Richard Gibson>
or I guess fn.tryCall(thisArgument, ...args)
18:18
<rbuckton>
i'd swap those two but sure (thisArg goes last, just like all the array methods, since it's more optional than args)
I disagree, thisArg should come first, like call and apply
18:19
<ljharb>
oh hm, i guess i see that argument too
18:19
<bakkot>
but also I would want a version that doesn't take this at all
18:19
<snek>
i get this feeling that like
18:19
<bakkot>
so specifically I think I like best .try, which is only useful for non-methods, and .tryCall, which adds an initial this parameter
18:19
<ljharb>
yeah if the thisArg has to come first in a call/apply variant then i'd def want yet another one that omitted the receiver entirely
18:19
<snek>
async modules are some sort of fundamental failure
18:19
<Richard Gibson>
yeah, that's the friction point
18:19
<snek>
because everyone keeps trying to build ways to disable them
18:19
<snek>
but i don't want them to be some sort of fundamental failure
18:19
<snek>
i really like them
18:20
<bakkot>
agreed on both points
18:23
<snek>
another place where async modules were no longer used https://github.com/WebAssembly/esm-integration/pull/76
18:24
<ljharb>
lol if we end up doing a directive, there's going to be a deluge of pragma/directive proposals once "no more modes" isn't a law anymore
18:25
<bakkot>
eh, I think directives which either evaluate normally or cause errors aren't really new modes
18:25
<nicolo-ribaudo>
This directive wouldn't be a new mode like strict vs sloppy
18:25
<nicolo-ribaudo>
It doesn't introduce a new "language version"
18:25
<nicolo-ribaudo>
No more than doing the same through any other syntax
18:25
<ljharb>
lol yeah i understand the nuance, but i don't think it will come across
18:29
<rbuckton>
export with { sync: true }? Not a huge fan of how it reads though.
18:29
<ljharb>
ps here's a relevant snippet from the notes from 2018 related to this exact problem: https://github.com/tc39/notes/blob/df1449925841bc77574e8e127611234670275575/meetings/2019-03/mar-28.md?plain=1#L507-L519
18:36
<snek>
oh man i would hate if nodejs was another reason for people to not use async modules
18:36
<snek>
that would kill me
18:40
<peetk>
what about a function or bit of syntax that {tells you whether/asserts !} you've hit the event loop? i feel like that's always what you really care about in these worker listener cases
18:48
<nicolo-ribaudo>
If you have multiple script tags the event loop will start spinning before that the module even starts running, so it would always say "yes"
18:50
<nicolo-ribaudo>
Example: you load an analytics script and your main entry point module, and the analytics script already spins the event loop
18:56
<peetk>
then that would be the correct result right?
18:56
<peetk>
if you are a worker and you need to register listeners to handle messages then you absolutely must register them before you hit the event loop
18:58
<Rob Palmer>
I see a barrel library
19:00
<ljharb>
deferred imports make me very nervous; a keyword on export that's a lazily evaluated thing on the first import seems pretty nice - it might turn treeshaking from a half-assed hack into a reliable mechanism
19:02
<Rob Palmer>
It's like runtime treeshaking. You only get the bits of the barrel you need.
19:03
<bakkot>
... treeshaking seems like a pretty reliable mechanism in the current world in fact?
19:03
<Rob Palmer>
And it can progressively load more of the barrel just-in-time
19:03
<ljharb>
... treeshaking seems like a pretty reliable mechanism in the current world in fact?
not in every codebase i've worked in; changing to deep imports and prohibiting barrel files has consistently produced MASSIVE savings
19:03
<ljharb>
coinbase's react native app size dropped by 71% (seventy-one, not a typo) with that change
19:04
<Jack Works>
it should really make defer become default back when tc39 design ES module in ES6
19:04
<Rob Palmer>
Tree-shaking only works when the compiler can see the full-scope of the program. That's not always possible in dynamically linked systems.
19:05
<Rob Palmer>
The perf issues of barrel files are well-documented https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/
19:05
<ljharb>
even then, my statement holds. coinbase's RN app had no dynamic linking.
19:05
<Jack Works>
and requires "sideEffects: false" in your package.json which is not everyone remembers to add
19:06
<ljharb>
and which isn't granular enough to support packages with one side-effecting code path and many non-side-effecting code paths, eg
19:07
<snek>
i can confirm, importing fewer things is faster
19:09
<Jack Works>
and which isn't granular enough to support packages with one side-effecting code path and many non-side-effecting code paths, eg
actually it supports, you can write "sideEffects": ["./src/init/**"]
19:10
<ljharb>
ooh, TIL, thanks
19:12
<bakkot>
coinbase's react native app size dropped by 71% (seventy-one, not a typo) with that change
that is wild to me. they just... had a bunch of side effects? for some reason?
19:13
<ljharb>
no, it's that treeshaking simply isn't capable of safely judging that, and so the necessary heuristics leave behind way more code than is actually used
19:13
<ljharb>
in the past, rollup's heuristics (when rollup was the only treeshaker) were too aggressive, and it broke airbnb.com in IE for months, so it's a really hard line to walk
19:13
<bakkot>
that has not been my experience at all; do you have an example where the heuristics fail?
19:14
<ljharb>
tbf i haven't dived into treeshaking tools to know exactly what it is; that's just the sense i have seeing the empirical results
19:15
<Rob Palmer>
Statically judging whether JS code has side-effects is hard - probably impossible - so compilers take a safety-first approach and bail out unless they are really sure dead code is safe to remove. This is why treeshaking is not always effective.
19:15
<Michael Ficarra>
the lesson here should be just don't use side effects
19:15
<snek>
i wonder if this js code halts
19:15
<ljharb>
the lesson most of us have taken is, don't use barrel files :-)
19:16
<snek>
i wish prepack was still a thing
19:18
<Jack Works>
the lesson most of us have taken is, don't use barrel files :-)
and with this proposal, barrel files suddenly becomes a performance benefit
19:19
<Ashley Claymore>
thankfully 0% of my code has side-effects, it's all overhead
19:19
<bakkot>
Statically judging whether JS code has side-effects is hard - probably impossible - so compilers take a safety-first approach and bail out unless they are really sure dead code is safe to remove. This is why treeshaking is not always effective.
Right but only if you're writing code in such a way that it's hard to analyze; my experience has been, dependencies which I consider of adequate quality to add to my projects are, in general, written in such a way that this is trivial to analyze
19:19
<bakkot>
I am surprised to learn this is not everyone's experience
19:20
<ljharb>
tbh i'm surprised it's anyone's. the above article got quite a lot of +1s on twitter and virtually no pushback that i saw
19:20
<Michael Ficarra>
after reading the article on what these are, I feel like this should have been obvious from the beginning
19:20
<Michael Ficarra>
people be crazy
19:21
<bakkot>
the article above was only talking runtime, not treeshaking
19:21
<bakkot>
in fact it specifically says "treeshaking solves this"
19:21
<bakkot>

you then run the bundled file to repeat the experiment and voilá, it finishes in a blink of an eye. Out of curiosity, you measure the time it takes to run esbuild and run the bundled file together and notice that both of them combined are still quicker than running the original source code. Huh? What is going on?

19:21
<Michael Ficarra>
"waah, I want one import line instead of five, I'm going to blindly merge others' namespaces, that seems like an okay trade-off"
19:22
<Michael Ficarra>
🤮
19:24
<ljharb>
ah fair, i was remembering some twitter discussion then i guess, not the article
19:26
<ljharb>
multiple exports was a mistake
19:36
<Michael Ficarra>
ugh we have a bug in PerformEval: it doesn't ever ToString its argument https://tc39.es/ecma262/#sec-performeval
19:37
<Michael Ficarra>
oh wait, never mind, it just can't reach that point without being a String
19:37
<Michael Ficarra>
I got it
19:41
<bakkot>
the thing where eval is the identity function on non-string arguments is... very strange
19:45
<littledan>
I prefer option 2 because it will extend to TT better
19:48
<Ashley Claymore>
function unsafeMaybeNotAString(v) {
  return Object.is(v, eval(v));
}
19:48
<Michael Ficarra>
ljharb: we need to figure out what we're going to do with iterator helpers ASAP
19:49
<Michael Ficarra>
the accessors approach seems safest to me, even if it makes us feel a little bit gross
19:49
<ljharb>
i'd love to hear v8's position on the two options, but i still don't feel that accessors are safer
19:49
<Michael Ficarra>
I won't die on that hill though; either solution works for now
19:50
<ljharb>
if we go with "omit" i will be happy to immediately make a new proposal to reintroduce them, and will coordinate with the person from the issue to track upgrading those sites, fwiw
19:50
<Michael Ficarra>
that doesn't mean you'll have engine support to experiment with shipping them though
19:50
<ljharb>
right, which is why i want to hear v8's position first
19:50
<Michael Ficarra>
Rezvan is the V8 rep at this meeting
19:51
<Michael Ficarra>
unsafeMaybeNotAString("v")
19:53
<Ashley Claymore>
function unsafeMaybeNotAString(v) {
  return Object.is(v, (0, eval(v)));
}
19:53
<Ashley Claymore>
Level 2
19:54
<rbuckton>
I'm generally partial to "omit" as well. While I understand bakkot's concerns around x.constructor === whatever, I think there is a fairly low likelihood of that becoming a problem if there is a short turnaround on a follow-on proposal and the outstanding sites being fixed.
19:54
<Ashley Claymore>
can strings be quines
19:56
<Ashley Claymore>
unsafeMaybeNotAString(`(function _(){return'('+_+')()'})()`)
19:56
<Michael Ficarra>
also the toString of an Iterator will change
19:56
<rbuckton>
Though I still think there's potential for a general solution to the "override mistake", such as adding a new PropertyDescriptor flag to opt-in to a behavior that allows setting on an instance even if the property is frozen on the prototype.
19:56
<rbuckton>
From what it is in the proposal spec, or do you mean from what it is on %IteratorPrototype% currently?
19:57
<Michael Ficarra>
from what's in the draft spec as of now
19:57
<Michael Ficarra>
it is proposed as "[object Iterator]" and will be "[object Object]" temporarily
19:58
<Michael Ficarra>
like... why do that?
19:58
<Michael Ficarra>
this is just unnecessary risk and I really don't see the upside other than warm fuzzies
20:01
<rbuckton>
I don't have a preference regarding toString/toStringTag, tbh. I generally wouldn't recommend relying on it for anything other than a debugging aid, so its presence or absence isn't that concerning to me.
20:32
<Rezvan>
right, which is why i want to hear v8's position first
V8 is supporting the PR (https://github.com/tc39/proposal-iterator-helpers/pull/287) Michael talked about yesterday. This incompatibility blocked us for shipping iterator helpers. The solution provided in the PR seems to be the best way to resolve the issue.
20:32
<ljharb>
V8 is supporting the PR (https://github.com/tc39/proposal-iterator-helpers/pull/287) Michael talked about yesterday. This incompatibility blocked us for shipping iterator helpers. The solution provided in the PR seems to be the best way to resolve the issue.
thanks; and does v8 have an opinion about, instead of the PR, just not implementing the constructor and Symbol.toStringTag properties for the time being?
20:33
<ljharb>
and additionally, with either approach, is Chrome willing to try to ship them as normal data properties in the future once the incompatible websites have finished upgrading?
20:51
<Rezvan>
thanks; and does v8 have an opinion about, instead of the PR, just not implementing the constructor and Symbol.toStringTag properties for the time being?
I am not sure about this. I do not remember anything related to this in our discussions with Shu.
20:52
<Rezvan>
and additionally, with either approach, is Chrome willing to try to ship them as normal data properties in the future once the incompatible websites have finished upgrading?
About this one, I think the answer is yes. Michael Ficarra Since you had discussions with Shu, what do you think about it?
20:59
<hax (HE Shi-Jun)>
slide link of next topic: https://johnhax.net/2023/slice/slide
21:00
<Michael Ficarra>
Rezvan ljharb we didn't talk about that
21:02
<Rezvan>
So, my answer was my opinion. Not sure if Shu thinks the same.
21:13
<Michael Ficarra>
I've never met anyone who knows the parameter meaning and order for both slice and splice off the top of their head
21:13
<Michael Ficarra>
other than slice(n), I look them up every time
21:13
<Chris de Almeida>
mdn. every time
21:14
<rbuckton>
mdn. every time
Quick-info in VS Code :)
21:14
<Chris de Almeida>
my vscode-fu need polishing for sure
21:14
<ljharb>
i know slice but only because i've trained myself to see the "p" so hard, at which point i immediately have to look it up every time
21:15
<rbuckton>
my vscode-fu need polishing for sure
Usually just array.slice( is enough assuming the language service knows array is an array type
21:15
<eemeli>
I can never remember which of the string methods support negative indices.
21:16
<Chris de Almeida>
Usually just array.slice( is enough assuming the language service knows array is an array type
oh true.. I guess I start thinking about it before I start writing the code.. like I don't get to slice( and then start wondering
21:16
<Michael Ficarra>
this vscode example doesn't look like a bug to me
21:16
<rbuckton>
Yeah, not a bug.
21:17
<rbuckton>
The line assumes subcommand exists as an element, because that's the only way you can get to that line to begin with.
21:18
<Michael Ficarra>
that's what I figured
21:29
<Michael Ficarra>
:-( my queue reply was skipped
21:29
<Chris de Almeida>
apologies Michael Ficarra didn't see your reply in time
21:30
<Michael Ficarra>
this is why we need https://github.com/bterlson/tcq/issues/65
21:31
<ljharb>
by "reified" do we mean like, a noun that represents a kind of "view" on an array/string?
21:31
<Michael Ficarra>
anyway, I was trying to point Ron to the https://github.com/tc39/proposal-iterator.range proposal where we have discussed having a reified range
21:32
<Michael Ficarra>
I personally support it, but most others seemed to reject it
21:32
<Michael Ficarra>
ljharb: basically a data structure that holds bounds and can be passed between the [] to do the slice
21:33
<ljharb>
yeah that sounds super unjavascripty to me
21:34
<rbuckton>

My suggestion for reification is the introduction of "inverted" get/set/has/delete via symbols that could be looked up on an Object in lieu of ToPrimitive:

class Index { 
   offset;
   fromEnd;
   [Symbol.geti](value) { 
     return this.fromEnd ? value[value.length - this.offset] : value[value.length];
   }
}

const x = new Index(1, /*fromEnd*/ true);
ar[x] // -> x[Symbol.geti](ar)

Which has a general utility for custom indexes, such as turning a WeakMap into a scoped pseudo-private name:

WeakMap.prototype[Symbol.geti] = function (key) { return this.get(key); }
WeakMap.prototype[Symbol.seti] = function (key, value) { this.set(key, value); }
const key = new WeakMap();
const obj = {};
obj[key] = 1; // key[Symbol.geti](obj);
21:34
<ljharb>
making an actual thing vs just typing x => x[a, b](y) doesn't seem worth adding
21:36
<bakkot>
I would definitely not want that to work with normal obj[key] access, but could see using the obj[^key] syntax or something
21:36
<rbuckton>
That can't be unwrapped though
21:36
<ljharb>
and while i see ron's point that making a special kind of value and a special protocol to go with it would be a logical generalization, that's a ton of complexity that it'd be a hard sell to justify
21:36
<bakkot>
though I don't see why you want to do it in this inverted way - it seems like usually containers know how to use an index, rather than an index knowing how to use a container?
21:36
<rbuckton>
i.e., x => x[a, b] doesn't let you extract a and b as there's no associated data model.
21:37
<ljharb>
so would we also have a PropertyKey that represents a property, takes an object, and does an inverted lookup?
21:37
<ljharb>
oh lol yeah sorry x.slice(a, b) in this case, i mistyped. but yes it doesn't let you get at the a and b, which seems like a benefit? (to make it an opaque lens)
21:38
<bakkot>
I think my preference here would be to leave obj[x] alone, and to add a new obj[^x] which invokes a symbol-named method on obj passing it x (uncoerced). there could then be a slice(a, b) function which returns a reified Slice, and the symbol-named method on Array could know how to deal with that
21:39
<bakkot>
also you could have the symbol-named method on Map be an alias for get, and that sort of thing
21:39
<rbuckton>
though I don't see why you want to do it in this inverted way - it seems like usually containers know how to use an index, rather than an index knowing how to use a container?
Except [] is only useful for strings and symbols (i.e., via toPrimitive). You can't put anything else meaningful in there that isn't either.
21:39
<bakkot>
and of course have a similarly named method to be invoked when doing obj[^x] = y
21:39
<Michael Ficarra>
yeah I would also not want a[b] delegating to a protocol
21:40
<rbuckton>
I'm surprised the WeakMap example isn't a stronger motivator?
21:40
<ljharb>
weak things are used so rarely i don't think it'd motivate any ergonomic changes?
21:42
<bakkot>
the WeakMap example seems actively bad to me? if I see obj[key] = 1 I am really expecting to set a property on obj. if it does something other than that this is bad.
21:43
<eemeli>
Could someone clarify if we're talking about just the slice 1:2 proposal now, or also the ^ one?
21:43
<ljharb>
whereas map[^key] = value seems nice (altho i'm not sure how it'd work with set)
21:44
<rbuckton>
the WeakMap example seems actively bad to me? if I see obj[key] = 1 I am really expecting to set a property on obj. if it does something other than that this is bad.
The WeakMap thing was a way to implement the "Private Symbol" mechanism championed by Alex Russel (I think?) without the need to introduce new syntax.
21:44
<bakkot>
ljharb: I am imagining that map[^key] = value is sugar for map[Symbol.set](key, value)
21:44
<ljharb>
i would agree
21:44
<waldemar>
Could someone clarify if we're talking about just the slice 1:2 proposal now, or also the ^ one?
It's ambiguous.
21:45
<rbuckton>
ljharb: I am imagining that map[^key] = value is sugar for map[Symbol.set](key, value)
That is at odds with the proposal for [^x] to mean index-from-end.
21:45
<ljharb>
right, we've been talking about a new imagined proposal for [^x] to not necessarily mean from end
21:45
<ljharb>
however arr[^-1] could map to .slice or .at, solving that problem
21:45
<bakkot>
rbuckton: not if Array.prototype[Symbol.set] = function(key, value) { key < 0 ? this[this.length + key] = value : this[key] = value }!
21:45
<rbuckton>
the WeakMap example seems actively bad to me? if I see obj[key] = 1 I am really expecting to set a property on obj. if it does something other than that this is bad.
I disagree. We already special case objects for indexed access. We just special case them via Symbol.toPrimitive
21:46
<rbuckton>
And an explicit Symbol.geti seems directly in line with "stop coercing things".
21:47
<bakkot>
"stop coercing things" is mainly intended to apply to new APIs, not to change the meaning of existing things
21:47
<bakkot>
I don't want to change the meaning of existing things as a rule
21:49
<bakkot>
but also, even in a world where I thought changing the meaning of obj[key] was a good idea, I would still think that it would invoke a method on obj, not a method on key
21:49
<rbuckton>
Honestly, I'd love to be able to hook a[x] directly to better support custom collections. Indexing with things other than string/number is fairly common in many languages, and is extremely limiting in JS. Symbol.geti avoids hooking all of a[x] for a specific class of key-like things.
21:50
<bakkot>
My suggestion is to add a new syntax for such cases.
21:50
<Michael Ficarra>
rbuckton: a[b] needs to have simple semantics
21:50
<rbuckton>
rbuckton: a[b] needs to have simple semantics
I don't agree.
21:50
<bakkot>
I don't think we should change a[x], but I think we could reasonably introduce a[^x], which would allow a to define how it should be indexed by arbitrary x
21:50
<rbuckton>
My suggestion is to add a new syntax for such cases.
This feels extremely wasteful, imo.
21:51
<Michael Ficarra>
sorry, a better thing to say would be "there needs to be property access with simple semantics; a[b] serves that purpose already"
21:51
<ljharb>
put more strongly, changing a[x] would likely break a lot of assumptions in existing code and in the minds of existing devs, and i doubt there would ever be consensus for that, such that i think it's a waste of time to pursue it.
21:51
<Michael Ficarra>
yeah, changing that is almost certainly dead in the water
21:52
<rbuckton>
I don't think we should change a[x], but I think we could reasonably introduce a[^x], which would allow a to define how it should be indexed by arbitrary x
If we introduced a new syntax for indexing, I'd be opposed to a[^x] specifically as I'd really like it to not mean different things in different places (negative index on Arrays, something else somewhere else, etc)
21:52
<ljharb>
if that were possible then i would assume we'd have added objects as object keys and not symbols
21:52
<ljharb>
If we introduced a new syntax for indexing, I'd be opposed to a[^x] specifically as I'd really like it to not mean different things in different places (negative index on Arrays, something else somewhere else, etc)
if it's a protocol, then that's inherently unavoidable. no?
21:52
<rbuckton>
Especially given the prior art for the ^x syntax in C#.
21:52
<littledan>
I was hoping that we could save prefix ^ for something more interesting, e.g., shorthand for zero-parameter arrow functions
21:52
<bakkot>
If we introduced a new syntax for indexing, I'd be opposed to a[^x] specifically as I'd really like it to not mean different things in different places (negative index on Arrays, something else somewhere else, etc)
sure, [^x] was just an idea. [@x] or something also fine.
21:53
<Michael Ficarra>
hax (HE Shi-Jun): I will definitely help, but I don't think I can sign up for co-championing
21:53
<ljharb>
or obj@[x], we could be creative
21:53
<Michael Ficarra>
there's only so much time I have to dedicate to TC39 stuff and I need to prioritise things
21:53
<ljharb>
(like protocols)
21:54
<rbuckton>
For partial application I'd considered an infix ~ as an indicator, i.e. a~(). I could see a~[x] as an option for custom syntax, but I still don't think custom syntax is merited if we could easily hook a[x] for a very specific set of things.
21:54
<Michael Ficarra>
lol tell me about it
21:54
<ljharb>
it's very very important that that syntax not be more hookable than it already is, getters are bad enough
21:54
<Michael Ficarra>
if iterator helpers and follow-ons weren't so damn useful and popular...
21:54
<hax (HE Shi-Jun)>
there's only so much time I have to dedicate to TC39 stuff and I need to prioritise things
Thank you ! I just try to advance it it the simple form which I believe is the best solution on these problems.
21:54
<rbuckton>
You could imagine Symbol as being implemented this way, and the addition of Symbol to x[a] was a change to an existing thing back in 2015
21:54
<Michael Ficarra>
or if someone else was working on them
21:55
<ljharb>
in the next half year i may have time to help with some of those, fwiw
21:55
<hax (HE Shi-Jun)>
I'm ok with other syntax, but to be honest I don't see how diff with a[^i].
21:55
<Michael Ficarra>
Thank you ! I just try to advance it it the simple form which I believe is the best solution on these problems.
I agree. I think simple slicing can be added on its own, with consideration for how it might fit into the generalisation in the future.
21:56
<hax (HE Shi-Jun)>
So it seems I have nothing could do...
21:56
<ethanarrowood>
No more ('b' + 'a' + + 'a' + 'a').toLowerCase()?
21:56
<rbuckton>
As a counterpoint to a[@x] or whatever. if x is a reified slice, I would want a way to explicitly throw when it is used via a[x], since that would be a mistake.
21:57
<ljharb>
reified properties/indexes/object operations don't sound like a great direction to me personally, we have functions for that.
21:57
<rbuckton>
If the things we want to use with a[@x] shouldn't be used with a[x], then we have two mutually exclusive syntaxes that arguably do the same thing depending on their inputs.
21:57
<rbuckton>
And that doesn't seem like a valuable use of our syntactic budget if we could merge them into just a[x].
21:58
<ljharb>
imo robustness of existing syntax > > > preserving syntax budget
21:58
<Michael Ficarra>
rbuckton: a[b] delegating to a symbol is just not going to happen
21:58
<rbuckton>
a[x] isn't robust, its wasteful
21:58
<ljharb>
lol what's wasteful about it as-is?
21:58
<ljharb>
(other than that getters exist)
21:58
<Michael Ficarra>
engines will not let that highly-optimised, extremely common operation become slow
21:59
<rbuckton>
That you can't use non-string, non-symbol values as keys withing them being stringified.
21:59
<rbuckton>
engines will not let that highly-optimised, extremely common operation become slow
They would be deoptimized in the same way for a[{}] today, since they already have to patch in a call to Symbol.toPrimitive.
21:59
<Michael Ficarra>
they may all be done by then 💪
22:00
<rbuckton>
So I don't imagine it would affect performance at all in the common case.
22:00
<Michael Ficarra>
only if anyone anywhere has ever touched Symbol.toPrimitive, which they usually haven't
22:01
<rbuckton>
only if anyone anywhere has ever touched Symbol.toPrimitive, which they usually haven't
There is plenty of code in the wild that is just depending on .toString() for the same thing
22:01
<rbuckton>
I'm using Symbol.toPrimitive as a general placeholder for all the things ToPropertyKey does for Object values
22:01
<Michael Ficarra>
🤷‍♂️ well it'll be up to you to convince engines of that then
22:11
<rbuckton>

To be clear, I haven't proposed geti because I think there are better solutions, though I do think geti has some interesting value on its own (i.e., using a WeakMap into a reified pseudo-private name). In general I'd love to be able to hook a[x] for custom collections, even if that were limited to only numeric string indexes as they are interpreted by Array and TypedArray, though that limitation wouldn't solve the same case as geti.

In lieu of geti/seti/etc., I'd be happy with a reified Slice and Index object that had specific, explicit handling by property evaluation to call a Symbol.slice/Symbol.splice or Symbol.indexGet/Symbol.indexSet on the object, such that x:y could produce a new Slice(x, y) and ^x could produce a new Index(x, /*fromEnd*/ true). Having reified slices and indexes is extremely convenient as they have a data model that can be interrogated and can thus be composed and reused in user code.

22:15
<Michael Ficarra>
rbuckton: I encourage you to participate in the Iterator.range issue tracker
22:18
<rbuckton>
rbuckton: I encourage you to participate in the Iterator.range issue tracker
How does this apply to Iterator.range? Do you have a particular issue this applies to? Ranges and slices are subtly different. I think I did bring up reified slices when Iterator.range was proposed, but I haven't had much time to follow the range proposal since then.
22:18
<ljharb>
reified ranges seem just as unjavascripty to me as reified slices
22:19
<Michael Ficarra>
rbuckton: I don't see how they are different
22:19
<rbuckton>
It's very pythony, and python and JS share many similarities.
22:19
<Richard Gibson>
string enums are nice until a change results in garbage like step 24 in https://tc39.es/ecma402/#sec-initializenumberformat
22:21
<rbuckton>
rbuckton: I don't see how they are different
Ranges produce values, slices represent ordinal positions from which to read and write chunks. There is a lot of similarity, but a Range that produces values can't really have a meaningful notion of "3 elements from the end"
22:21
<rbuckton>
Hence why I said they are subtly different
22:21
<Michael Ficarra>
I hadn't considered that a reified slice would try to represent relative indexing
22:22
<rbuckton>
That was in the slides.
22:22
<rbuckton>
x:^y
22:22
<Michael Ficarra>
lol sure, there was a lot of things in the slides
22:23
<rbuckton>
The slice syntax is essentially expr:expr, so 0:1 or x:y. If you want to represent .slice(0, -1), you use x:^y
22:24
<rbuckton>

Mostly to avoid the disparity with a[-1], and to align with a[^1]. It also keeps them visually similar:

a[0:^1]
a[0]
a[^1]
22:25
<rbuckton>
Thus, a Slice(x, y) is actually a pair of Index objects. x:^y would be reified as new Slice(new Index(x, false), new Index(y, true)). At least, that's how it works in C#.
22:26
<hax (HE Shi-Jun)>
Refication of index/range are good I believe, the problem is we never have such things in js, all js current methods are just calculate eaglely, for example, this is how resizeable typedarray deal with negative index.
22:27
<rbuckton>

That also ensures the types are consistent and the object model is more user friendly if you want to do something like:

const x = foo ? 0 : ^1;
const s = x:^y;
s.start; // an Index, no need to do a typeof test
s.end; // also an Index
22:27
<hax (HE Shi-Jun)>
Another problem, if there will be index/range I will really hope they are new primitive types. But it seems very impossible as current status.
22:28
<rbuckton>
Refication of index/range are good I believe, the problem is we never have such things in js, all js current methods are just calculate eaglely, for example, this is how resizeable typedarray deal with negative index.
I could see a reified slice of x:y, and a reified range like x..y as two distinct things.
22:28
<rbuckton>
Another problem, if there will be index/range I will really hope they are new primitive types. But it seems very impossible as current status.
Primitives aren't really necessary for this. Regular expression literals produce Objects
22:29
<rbuckton>
Though == and !== might be nice, most other operators don't apply to slices and indexes.
22:29
<rbuckton>
You can't really compare ^0 and ^1 using < or > as they may point to the same element if the array only has a single element.
22:30
<hax (HE Shi-Jun)>
Primitives aren't really necessary for this. Regular expression literals produce Objects
yeah. of coz we can always use object.
22:31
<rbuckton>
Also, range might accept floats, while a slice should only accept integral number values, bigint, and Index, and not coerce
22:32
<rbuckton>
Also, a slice of x:y could theoretically hold a non-primitive, so it can't be a primitive.
22:32
<Michael Ficarra>
does this not fall under the "don't coerce objects to primitives"?
22:33
<rbuckton>
does this not fall under the "don't coerce objects to primitives"?
Are you asking me, regarding Slice/Index, or something else?
22:34
<Michael Ficarra>
no, I'm talking about the coercion slides
22:34
<Michael Ficarra>
null -> some primitive
22:36
<Michael Ficarra>
these tables are soooooo helpful
22:53
<rbuckton>
"you haveundefined notifications to review"
22:53
<rbuckton>
(or NaN)
22:55
<rbuckton>
IMO, the value for Numbers and BigInts taking strings makes sense given the DOM return value rationale
22:58
<Michael Ficarra>
yeah numeric strings are all over the place in the DOM, and JS kinda has to work nicely with the DOM or people will get mad
22:59
<eemeli>
"you haveundefined notifications to review"

Hopefully in an Intl.MessageFormat world, that message would read:

you have {$count} notifications to review
23:00
<rbuckton>

Hopefully in an Intl.MessageFormat world, that message would read:

you have {$count} notifications to review
In an ideal world, yeah maybe. In the current world, messages like that are so commonplace they're a meme
23:09
<bakkot>
As a counterpoint to a[@x] or whatever. if x is a reified slice, I would want a way to explicitly throw when it is used via a[x], since that would be a mistake.
just make the Symbol.toPrimitive method on the Slice class throw?
23:09
<bakkot>
that seems fairly straightforward to me, not really a counterpoint
23:09
<bakkot>
but, I also don't see why it would be a mistake?
23:09
<bakkot>
like if the class knows how to handle slices, like Array, then it would just give you the slice
23:10
<bakkot>
from its Symbol.get method
23:11
<rbuckton>
but, I also don't see why it would be a mistake?
If Slice needs to be used with a[@x], then using it with a[x] is 100% a bug.
23:13
<bakkot>
oh, sorry, I misread
23:13
<bakkot>
yes, using with a[x] is a bug, and would be covered by having slice's Symbol.toPrimitive throw
23:13
<rbuckton>
just make the Symbol.toPrimitive method on the Slice class throw?
Yes, that's what I'm getting at. This message leads into the point I was trying to make in the subsequent message. What you use in a[x] and what you would use in a[@x] would be mutually exclusive to avoid bugs. If they are mutually exclusive, then just having a[x] do both would avoid the concern about trying to pick the right one to avoid a bug.
23:13
<bakkot>
ah, well, they would not be totally mutually exclusive
23:13
<bakkot>
because Array could have a[@-1] do negative indexing
23:14
<rbuckton>
ah, well, they would not be totally mutually exclusive
They should be.
23:14
<bakkot>
they would be mutually exclusive for object types though, yes
23:14
<bakkot>
why? if I do map[@"string"], that can do a map lookup
23:14
<rbuckton>
because Array could have a[@-1] do negative indexing
And a[-1] would not, thus its still a potential bug
23:14
<bakkot>
Well, yes, a[-1] would be a bug but in general passing things to the wrong place is a bug
23:15
<rbuckton>
why? if I do map[@"string"], that can do a map lookup
Because its too easy to write map["string"] and it mean something different.
23:15
<bakkot>
If that's true it applies to any special syntax for indexing a map. I don't agree.
23:16
<rbuckton>
I'm not advocating for indexing a map using [].
23:16
<bakkot>
Right, but I am
23:16
<bakkot>
and above you were advocating for a way to index collection types by non-string values; Map seems like the obvious collection type you'd most want to index by non-string values
23:17
<rbuckton>
I'm advocating for doing something meaningful with objects in a[x] aside from just ToString().
23:18
<bakkot>
What problems does that solve?
23:20
<bakkot>

My suggestion is, introduce a new obj[@x] syntax which invokes a symbol-named method on obj with x, and also obj[@x] = y which invokes a symbol-named method on obj with x and y, which solves

  • you can do negative indexing on arrays
  • you can assign at negative indexes on arrays
  • if we have a slice type, you can slice arrays with syntax
  • you can do Map read/writes with syntax
  • other collection types can defining indexing behavior for non-string values

so it gives you a lot of stuff!

23:20
<rbuckton>

It solves reified Slice, reified Index, WeakMap as a scoped private name, plus a number of other approaches such as a "pick" object, i.e.:

const obj = { x: 1, y: 2, z: 3, w: 4 };
const obj2 = obj[new Pick("x", "y", "z")];
obj2; // { x: 1, y: 2, z: 3 }
23:21
<bakkot>
Your suggestion only solves reified Index if either there is also an "indexing" protocol collection types can opt into, or the Index type knows about every single kind of collection
23:21
<rbuckton>
I was also considering geti as an approach to handling concurrent reads/writes in multithreaded javascript with Worker and shared structs, though I'm also looking into .{ notation as a solution
23:21
<bakkot>
Ditto pick, etc
23:21
<rbuckton>
Yes, I expected there would also be an indexing protocol, just like slice notation added a slice protocol.
23:21
<bakkot>
and slice
23:22
<rbuckton>
Pick, not so much.
23:22
<rbuckton>
Slice depends on knowing something about the size of a collection. Pick does not.
23:22
<bakkot>
OK, so, my proposal does not require having an indexing protocol, or a slice protocol. If there is a slice type, and a pick type, then collections can define for themselves what to do when asked to "slice" or "pick" something, without needing a protocol. That seems better.
23:23
<rbuckton>
I don't agree that it's better.
23:23
<rbuckton>
Slice and index-from-end have a well-defined protocol that multiple different objects could implement.
23:24
<bakkot>
The way I am suggesting they implement that is, they define a Symbol.get method which has specific behavior for the reified slice values.
23:24
<rbuckton>
If I have a Slice and use it on an array via a[@x], I get a slice. If I used it on a Map via a[@x], it might just act as a key, thus it's not interpreted as a slice. That means that a[@x:y] would do something strange on a Map.
23:25
<bakkot>
Yes, if you use a key with a type of collection which does not know how to interpret that key, you're going to have a bad time. But this is also true in your inverted world if you use a key with a type of collection where the key does not know how to interpret that type of collection.
23:26
<rbuckton>
Ignoring the @ for a second, a[x:y] and a[^x] should always be interpreted as "take a slice from a" and "get the element from a relative from its end".
23:27
<rbuckton>
Yes, if you use a key with a type of collection which does not know how to interpret that key, you're going to have a bad time. But this is also true in your inverted world if you use a key with a type of collection where the key does not know how to interpret that type of collection.
That's why I'm not proposing a general indexing hook. I'm proposing a very specific indexing hook.
23:28
<bakkot>
Ignoring the @ for a second, a[x:y] and a[^x] should always be interpreted as "take a slice from a" and "get the element from a relative from its end".
I am proposing not to have either a[x:y] or negative-indexing a[^x]. I think that having only a general a[@x] hook would solve the use cases for both a[x:y] and a[^x].
23:28
<rbuckton>

With geti, Slice has a very specific and well-defined interaction with something like Array:

Slice.prototype[Symbol.geti] = function (value) {
  return value[Symbol.slice](this.start.getIndex(value), this.end.getIndex(value));
}
Array.prototype[Symbol.slice] = function (start, end) {
  return this.slice(start, end);
}
23:30
<rbuckton>

The same goes for Index:

Index.prototype[Symbol.geti] = function (value) {
  return value[Symbol.indexGet](this.getIndex(value));
}
Array.prototype[Symbol.indexGet] = function (index) {
  return this[index];
}
23:31
<rbuckton>
(or something to that effect)
23:31
<bakkot>
With my proposal also: Array.prototype[Symbol.get] = function(v) { if (Slice.isSlice(v)) return this.slice(v.start, v.end); /*...*/ }
23:31
<bakkot>
And it only requires a single protocol, not two.
23:33
<rbuckton>

But a WeakMap could do:

WeakMap.prototype[Symbol.geti] = function (value) {
  return this.get(value);
}

and a Pick might look like:

class Pick {
  keys;
  constructor(...keys) {
    this.keys = keys;
  }
  [Symbol.geti](value) {
    var obj = {};
    for (var p of Reflect.ownKeys(value)) {
     if (this.keys.includes(p)) obj[p] = value;
    }
    return obj;
  }
}
23:34
<bakkot>
(I really do not think it makes sense for keys to define how they interact with collections, rather than collections defining how they interact with keys.)
23:34
<rbuckton>
With my proposal also: Array.prototype[Symbol.get] = function(v) { if (Slice.isSlice(v)) return this.slice(v.start, v.end); /*...*/ }
That doesn't solve a[@x:y] potentially not returning a Slice and maybe doing something else instead of erroring in the case of a Map.
23:35
<bakkot>
I am specifically not proposing to have a[@x:y] syntax
23:35
<rbuckton>
(I really do not think it makes sense for keys to define how they interact with collections, rather than collections defining how they interact with keys.)
I would much rather be able to hook indexing, but not at the cost of the cognitive overhead of picking the correct indexing operator or introducing subtle bugs.
23:36
<bakkot>
But yes, if you have a reified slice type, and you try to index a map by that, the appropriate behavior is to look up the slice instance in the map. That's how maps work.
23:37
<rbuckton>
And as I said, I'd be happy to have Slice and Index have special handling in indexers even without the symbols. That's what C# does. I just feel that geti adds a bit more flexibility.
23:37
<rbuckton>
But I don't think leveraging a general indexing hook to handle Slice and Index is a good idea.
23:38
<rbuckton>
But yes, if you have a reified slice type, and you try to index a map by that, the appropriate behavior is to look up the slice instance in the map. That's how maps work.
No. The appropriate behavior for a[x:y] (or a[@x:y]) on a Map that doesn't undertand how to handle a slice should be to throw. Not silently do the wrong thing.
23:42
<rbuckton>
Or if you expect a[@x:y] to just be a generalized map indexer, then I'd still strongly prefer we have specific handling for a[x:y] and a[^x], such that slicing with the correct syntax is always slicing, and that a[@x] just doesn't care at all what you put into it. Documentation that describes slicing with syntax should always refer to a[x:y], and it should never be recommended that you abuse something like Symbol.get/a[@x] to handle slicing. It's just a bug farm and a source of confusion.
23:47
<rbuckton>
C# lets you write map[0..1] as a key, but C# is typed and thus you know that the input/output is when indexing using the key type. JS isn't typed, so its far easier to do the wrong thing.
23:56
<rbuckton>
As an aside, I'm not sure what symbol you would want to use for a[@b]? You can't use @ because of decorators, and I'd prefer to keep ^x for reified relative index if possible, so it either has to be an infix operator, or a prefix operator that comes before the [. I'd like to avoid using & and * due to both their precedence for use with pointers in other C-like languages, and that I'd really like to use & for the ref proposal if a ref keyword ends up not being feasible (since ref has a close enough similarity to pointers that the semantic meaning seems reasonable).
23:58
<rbuckton>
% may be out as well. We'd considered it for pipeline, but IIRC % is hard to type on a number of international keyboard layouts.