tc39 / proposal-extractors

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

Type systems #4

Closed dwelle closed 5 months ago

dwelle commented 1 year ago

In TypeScript and other type systems, as I understand, the parameter/argument type would need to be different from the value type in the function body for example, where the type would come from the extractor's return value.

I'm not seeing any blockers here, but might be a bit confusing.

function toDateObject = (timestamp: number) {
  return new Date(timestamp);
}

class Book {
  public createdAt: Date;

  constructor({
    toDateObject(createdAt)
  }: {
    createdAt: number
  }) {
    this.createdAt = createdAt // createdAt is a Date object at this point
  }
}

new Book({ createdAt: 1663247804581 });
Jamesernator commented 1 year ago

In a lot of ways, changing the type is already fairly similar to what default parameters already do:

declare const o: { x?: number };
const { x=3 } = o;
// x is number here, not number|undefined

Obviously removing undefined from a type is a lot narrower than generally changing the type, but I don't think it's too surprising.

In fact one could even model a default value as an extractor:

const default3 = {
    [Symbol.matcher](v: number|undefined): number {
        return { matched: true, value: v ?? 3 };
    }
}

const { x: default3(x) } = o;
rbuckton commented 5 months ago

In TypeScript, we could make inference aware of the inverse relationship between an extractor return type and its input argument:

const toDateObject = {
  [Symbol.customMatcher](subject: number | string | Date): [Date] | false {
    const result = subject instanceof Date ? result : new Date(subject);
    return !isNaN(result.valueOf()) && [result];
  }
};

function f({ createdAt: toDateObject(createdAt) }) {
  return createdAt;
}

Here, we would infer Date for createdAt inside of f, and { createdAt: number | string | Date } for f's first parameter.

Also, there is a typo in your example that may be the source of confusion here:

  class Book {
    public createdAt: Date;

    constructor({
-     toDateObject(createdAt)
+     createdAt: toDateObject(createdAt) 
    }: {
      createdAt: number
    }) {
      this.createdAt = createdAt // createdAt is a Date object at this point
    }
  }