tc39 / proposal-extractors

Extractors for ECMAScript
http://tc39.es/proposal-extractors/
MIT License
200 stars 3 forks source link

Extractors and property declaration #15

Open codehag opened 4 months ago

codehag commented 4 months ago

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.

rbuckton commented 4 months ago

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.

codehag commented 4 months ago

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.

rbuckton commented 4 months ago

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.