Extractors and property declaration
In the example, we have
class Book {
constructor({
// ...
// Extract `createdAt` as an Instant
createdAt: InstantExtractor(createdAt) = Temporal.Now.instant(),
modifiedAt: InstantExtractor(modifiedAt) = createdAt
}) {
// ..
this.createdAt = createdAt;
this.modifiedAt = modifiedAt;
}
}
If I understand correctly, the default binding of createdAt is set to InstantExtractor(createdAt) = Temporal.Now.instant(), however, any other value that is passed will not be extracted? Is the existing function shorthand for objects blocking us from allowing extractors like this? Is it possible or desired to have extractors execute on each function call?
constructor({
// ...
// Extract `createdAt` as an Instant
InstantExtractor(createdAt) = Temporal.Now.instant(),
InstantExtractor(modifiedAt) = createdAt
}) {
There is some similar potential confusion here as with the assignment let InstantExtractor(createdAt) = ..., but I am wondering if we have spec machinary that makes this particularly difficult or if we are avoiding it for other reasons.
In the example, we have
class Book { constructor({ // ... // Extract `createdAt` as an Instant createdAt: InstantExtractor(createdAt) = Temporal.Now.instant(), modifiedAt: InstantExtractor(modifiedAt) = createdAt }) { // .. this.createdAt = createdAt; this.modifiedAt = modifiedAt; } }If I understand correctly, the default binding of createdAt is set to InstantExtractor(createdAt) = Temporal.Now.instant(), however, any other value that is passed will not be extracted?
I think you may be misunderstanding the example. Consider the following destructuring syntax:
const { createdAt: createdAtVar = Temporal.Now.instant() } = obj;
Here we are binding obj.createdAt property to a createdAtVar identifier, with Temporal.Now.instant() as a default to evaluate should obj.createdAt be undefined.
You can replace createdAtVar with a BindingPattern for further destructuring, including {}, [], or an extractor:
const { createdAt: InstantExtractor(createdAtVar) = Temporal.Now.instant() } = obj;
Here we are binding object.createdAt through InstantExtractor into createdAtVar. If obj.createdAt is undefined, we evaluate the initializer and pass that value through the extractor.
Is the existing function shorthand for objects blocking us from allowing extractors like this? Is it possible or desired to have extractors execute on each function call?
I'm not clear on what you're asking here? Extractors will evaluate each time the declaration is evaluated.
constructor({ // ... // Extract `createdAt` as an Instant InstantExtractor(createdAt) = Temporal.Now.instant(), InstantExtractor(modifiedAt) = createdAt }) {There is some similar potential confusion here as with the assignment
let InstantExtractor(createdAt) = ..., but I am wondering if we have spec machinery that makes this particularly difficult or if we are avoiding it for other reasons.
I did not use this syntax because it conflates the property and the binding. We allow that for shorthand assignments because there's a direct correspondence between the property value you read and the binding you create (i.e., { x } is equivalent to { x: x }). We don't do that for other binding patterns, though, as there potentially a disconnect between the input and output. For example, these are illegal today as they may be nonsensical or confusing:
const { { x } } = ...
const { [x] } = ...
Extractors are very much like array destructuring and it's far more likely that the output of a given extractor does not directly correspond to the input as it's often only a portion of the input. I don't think the syntax would be difficult to achieve, but I didn't include it because it doesn't correctly align with how binding patterns already work and doesn't match the majority use case.
Perhaps it's worth considering in the future, but I don't believe { F(x) } is necessary for the MVP given that { x: F(x) } works and is less confusing.
I see. This weirdly makes me even more uncomfortable with what is being proposed because it builds on the aliasing of variables in destructuring which is not well understood by developers. But I do understand the logic behind it and why it is self consistent.
For an example of consistency, you can roughly emulate extractors in an AssignmentPattern (but not a BindingPattern) with some convoluted code:
let x1, y1, x2, y2;
// using extractors
({
p1: Point(x1, y1),
p2: Point(x2, y2)
} = line);
// using an object literal
({
p1: {
set value(subject) {
[x1, y1] = Point[Symbol.customMatcher](subject, "list");
}
}.value,
p2: {
set value(subject) {
[x2, y2] = Point[Symbol.customMatcher](subject, "list");
}
}.value
} = line);
As such, the runtime semantics of extractors slot in place of the convoluted assignment to { ... }.value as a more convenient mechanism that remains consistent with destructuring, as well as to extend those semantics to binding patterns.