-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Slice Extensibility #19
Comments
Was this meant to be |
Yes, thanks. I've updated the issue. |
"slice" to me makes no sense as a concept applied to things that aren't lists (such as arrays, strings, Sets) or things without indexes. If we want a generic extraction API, we should call it something else, and it shouldn't solely use numbers. |
@ljharb we can bikeshed on |
I wonder if we might want to dust off the // built-in `Range` class
class Range {
constructor(start = 0, end = -1, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.geti](obj) {
return obj[Symbol.slice](this.start, this.end, this.step);
}
[Symbol.seti](obj, values) {
return obj[Symbol.splice](this.start, this.end, values);
}
}
// Literal `Range` syntax:
let range = 1:3;
// -> range = new Range(1, 3);
// Get a range
let source = [1, 2, 3, 4, 5];
let chunk = source[range];
// -> chunk = range[Symbol.geti](source);
// -> chunk = source[Symbol.slice](1, 3, 1);
// -> chunk = [2, 3]
source[range] = [7, 8, 9];
// -> range[Symbol.seti](source, [7, 8, 9])
// -> source[Symbol.splice](1, 3, [7, 8, 9])
console.log(source); // 1, 7, 8, 9, 4, 5 While there would definitely be some indirection under the covers, its very flexible, consistent, and cohesive. One caveat is that a literal range syntax would be ambiguous in a conditional, so you would have to require parens for a literal range expression (e.g. |
@rbuckton nice idea Most other languages have that
|
This seems like it would be too complicated for the array selector case, compared to this: const ints = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const odds = ints[0::2]; // [1, 3, 5, 7, 9];
const events = ints[1::2]; // [2, 4, 6, 8, 10]; Besides, you could already map with const odds = Array.from([0:5], i => (i * 2) + 1); |
The problem with adding a new Range primitive is that would complicate GetValue/PutValue, regressing performance for all property access. The win with just the slice notation is that it's just syntax which can be directly rewritten in the parser to be a call out to Symbol.slice and we can reuse all the magic sauce we have with optimizing regular property access. You only pay for call out to Symbol.slice if you use slice notation, not every property access. Also, since this is just syntax, we can easily optimize this with ICs. |
Hosts like v8 and Chakra already optimize property access and have opt-outs for non-PropertyKey values (e.g. |
There's always at least an extra type check (load + jump) required to bailout on the fast path. |
also and |
In addition to the issues Sathya raised, it seems somewhat complicated grammatically to give |
This possible new meaning for @rbuckton I think it's very frequent to need a |
That's what I'm proposing, but not what @rbuckton seems to want according to #19 (comment). |
@gsathya: I assume you are referring to this:
To avoid ambiguities with conditional and labels, we could restrict ranges to element access ( Also, if // create array
const ar = [...(1:5)]; // [1, 2, 3, 4]
// or, allow without parens in array
const ar = [...1:5]; // [1, 2, 3, 4]
for (const x of (0:10)) { // 0, 1, 2, ..., 9
} |
I don't expect
I see how this range literal is fitting well in this proposal, this looks great |
Except that iterating over a Range would be far less memory intensive: for (const x of (0:Number.MAX_SAFE_INTEGER)) {
// only need to hold four numbers (start, end, increment, and current) and the Range object in memory
}
for (const x of [0:Number.MAX_SAFE_INTEGER]) {
// need to hold an Array object with 9,007,199,254,740,991 numbers in memory!
} |
@rbuckton would the range literal expose methods like This would be interesting: (1:8).map(x => x**2)
(0:5).map(i => (0:5).map(j => 5*i+j)) If not, it's still possible to spread it of course [...1:8].map(x => x**2)
[...0:5].map(i => [...0:5].map(j => 5*i+j)) |
No, I wouldn't expect it to. |
|
I'm confused, why would we want a splice symbol? splice is abomination. |
It seems odd to have |
I find the former intuitively useful and the latter violently unpalatable; i don't see an advantage to syntax that creates a ton of observable operations and also represents what's become a very unidiomatic pattern (optional chaining has no plans to add optional assignment, for comparison). |
I feel |
A syntactical expression ( The biggest benefit, for me at least, is the range creation discussed in this issue, because |
Don't make me wrong. I think |
@rbuckton what does the Other thing, for const a=[]; a[2:6:2] = 4; // a will be [undefined,undefined,4,undefined,4] or still [] ?
const a=[1,1,1,1]; a[1:3] = [2, 4]; // would an array be 'spread'?
// so a would be [1,2,4,1]? or [1,[2,4],[2,4],1] I guess the latter, so it could only assign a same value to a range of indexes Concerning the Follow-on Proposals: 2.2 Personally I'd drop them, (so 2.3. For the naming, Interval sounds too generic since it's a more particular integer interval here, Sequence could fit, but I think we should keep Range/range, and maybe have it attached to Array, Hope we can merge that to the proposal, I was trying to see how to implement a babel plugin for it |
In this case, "inverted". Basically, the semantics of A good example for WeakMap.prototype[@@geti] = function (target) { return this.get(target); }
WeakMap.prototype[@@seti] = function (target, value) { this.set(target, value); }
const weakPropertyX = new WeakMap();
const obj = {};
obj[weakPropertyX] = 1;
console.write(obj[weakPropertyX]); // prints 1 There are plenty of other use cases for function pick(...names) {
return {
[Symbol.geti]: (obj) => names.reduce((result, name) => (result[name] = obj[name], result), {}}
[Symbol.seti]: (target, source) => { for (const name of names) target[name] = source[name]; }
};
}
const obj = { a: 1, b: 2, c: 3 };
// pick properties to read from `obj`
const obj2 = obj[pick("a", "c")];
obj2; // { a: 1, c: 3 };
// pick properties to write to 'obj'
obj[pick("a", "b")] = { a: 4, b: 5 };
obj; // { a: 4, b: 5, c: 3 } The |
@ljharb while I understand your concern about
|
so I guess and It's another reason to not apply this slice-notation to strings, since setter/splice wouldn't make sense for them. But it would still be very interesting to have |
|
Also, removing a section of the array could be something like |
but splice wouldn't support the step (in
but if we ever want to change an array, we can always do But I admit with slice only we can't do the second example (insert items every step), so I'm neutral for |
Could it work in destructuring? like so: const a = [1,2,3,4,5];
const {[0:-1]: a1, [a.length-1]: last} = a;
// a1 == [1,2,3,4]
// last == 5 // this already works |
C# 8 has added ranges and indexes, which includes both syntax and types for these behaviors:
|
I'll start writing a babel plugin for it |
I did a polyfill with acorn: https://github.com/brigand/jellobot/pull/31/files#diff-a1284a77ff99b45ce588591eddda54a9, I'll try with babel later |
In light of #30, I've been tinkering with what this might look like in ECMAScript: https://gist.github.com/rbuckton/174b02d2a43573627201f8057701044c:
The The
let ar = ["a", "b", "c", "d"];
let m1 = ^1;
// --> new Index(1, "end");
ar[m1]; // "d"
// --> m1[Symbol.geti](ar)
// --> ar[Symbol.indexedGet](m1)
// --> ar[m1[Symbol.index](ar.length)]
// --> ar[ar.length - 1]
// --> ar[4 - 1]
// --> ar[3]
// --> "d"
let ar = ["a", "b", "c", "d"];
let r = (0:^1);
// --> new Interval(new Index(0, "start"), new Index(1, "end"))
ar[r]; // ["a", "b", "c"]
// --> r[Symbol.geti](ar)
// --> ar[Symbol.slice](r)
// --> Slice of `ar` for `r[Symbol.interval](ar.length)` as ([start, end, step])
// --> Slice of `ar` for `[r.start[Symbol.index](ar.length),
// r.end[Symbol.index](ar.length),
// r.step]` as ([start, end, step])
// --> Slice of `ar` for `[0, ar.length - 1, 1]` as ([start, end, step])
// --> Slice of `ar` for `[0, 4 - 1, 1]` as ([start, end, step])
// --> Slice of `ar` for `[0, 3, 1]` as ([start, end, step])
// --> ["a", "b", "c"] Host engines like V8 could choose to optimize code paths during compilation to remove the reification of (edit: switched from |
What's the advantage of Is your idea to completely avoid this notation for strings, since assignment expressions wouldn't make sense for them (we can also add the arguments of https://github.com/tc39/proposal-slice-notation#should-we-ban-slice-notation-on-strings)? I feel like it'd be good to avoid introducing new built-in objects (to reduce the "cost" and complexity for this proposal), I thought about a I implemented slice-notation/slice-expression in https://github.com/engine262/engine262/pull/89/files#diff-7a3164ab8de945e8bd82f29aa3f3b300R10-R27 if (expression.type === 'SliceExpression') {
let start, end, step;
if (expression.startIndex){
const startPropertyRef = yield* Evaluate(expression.startIndex);
start = Q(GetValue(startPropertyRef));
}
if (expression.endIndex) {
const endPropertyRef = yield* Evaluate(expression.endIndex);
end = Q(GetValue(endPropertyRef));
}
if (expression.step) {
const stepPropertyRef = yield* Evaluate(expression.step);
step = Q(GetValue(stepPropertyRef));
}
const bv = Q(RequireObjectCoercible(baseValue));
const slice = Q(GetMethod(bv, wellKnownSymbols.slice));
// #sec-call
return Call(slice, Value.undefined, [start, end, step]);
} It's slightly limited compared to a arr[(() => (0:2))()] // would not be like arr[0:2]
// it'd be like arr[ToString((() => (0:2))())] rather Because we don't evaluate/resolve the SliceExpression like we could with a built-in object, but I don't think it's an issue, this feature is intended to be used 'statically' |
One motivator for |
Well true, I don't it's possible to extend There's https://github.com/keithamus/proposal-array-last proposing an I like this EDIT: it seems [...'banana'].indexOf('a', -3)
// 3 but only for 'banana'.indexOf('a', -3)
// 1
|
Given the feedback in this thread,
|
Yes, I agree, and I'd still prefer to handle those cases without additional built-ins (or at least less additional built-ins) The specific behavior for String you described, will be inlined in |
@rbuckton Any update? It seems there are too many things we want to add, maybe we can minimize them and write a separate proposal? For example, we can first specify
and leave all other things like |
I think the idea was to desugar the reverse index syntax and the index range syntax to |
It would be great if you could specify how the slice notation should apply to an object, perhaps via a
Symbol.slice
:Then syntax like this:
Becomes this at runtime:
The advantage of this is that we can specify the syntax in terms of a method, which allows us to specify the behavior of the slice notation on strings to work over code points rather than characters, and the behavior of the slice notation on typed arrays.
In addition, users can define how the slice notation applies to their own classes:
The text was updated successfully, but these errors were encountered: