tc39 / proposal-type-annotations

ECMAScript proposal for type syntax that is erased - Stage 1
https://tc39.es/proposal-type-annotations/
4.27k stars 47 forks source link

Higher Kinded Types #152

Open Caleb-T-Owens opened 2 years ago

Caleb-T-Owens commented 2 years ago

Hello, I just wanted to draw attention to https://github.com/microsoft/TypeScript/issues/1213 which outlines discussion about syntax for higher kinded types which greater enable the implementation of chainable APIs

For example: this implementation of a chainable object will revert back to the super type Mappable<B> when .map() is called on OtherMappable

class Mappable<A> {
    constructor(protected value: A) {}

    newThis<B>(b: B) {
        return new Mappable<B>(b)
    }

    map<B>(fn: (a: A) => B): Mappable<B> {
        return this.newThis(fn(this.value))
    }
}

class OtherMappable<A> extends Mappable<A> {
    newThis<B>(b: B) {
        return new OtherMappable<B>(b)
    }

    getValue() {
        return this.value
    }
}

console.log(
    new OtherMappable(3)
        .map((a) => a + 1)
        .getValue() // Property 'getValue' does not exist on type 'Mappable<number>'.(2339)
)

The proposed syntax would look like

class Mappable<A> {
    constructor(protected value: A) {}

    newThis<B>(b: B) {
        return new Mappable<B>(b)
    }

    map<B, F<~> extends Mappable<~>>(fn: (a: A) => B): F<B> {
        return this.newThis(fn(this.value))
    }
}

class OtherMappable<A> extends Mappable<A> {
    newThis<B>(b: B) {
        return new OtherMappable<B>(b)
    }

    getValue() {
        return this.value
    }
}

console.log(
    new OtherMappable(3)
        .map<number, OtherMappable>((a) => a + 1)
        .getValue()
)

whereas, without, you have to follow a pattern like this:

interface HKT {
  input: unknown,
  output: unknown
}

type CallHKT<F extends HKT, I> =
  (F & { input: I })["output"]

interface MappableHKT extends HKT {
    output: Mappable<this["input"]>
}

class Mappable<A> {
    constructor(protected value: A) {}

    newThis<B>(b: B) {
        return new Mappable<B>(b)
    }

    map<B, MKT extends MappableHKT>(fn: (a: A) => B): CallHKT<MKT, B> {
        return this.newThis(fn(this.value))
    }
}

interface OtherMappableHKT extends MappableHKT {
    output: OtherMappable<this["input"]>
}

class OtherMappable<A> extends Mappable<A> {
    newThis<B>(b: B) {
        return new OtherMappable<B>(b)
    }

    getValue() {
        return this.value
    }
}

console.log(
    new OtherMappable(3)
        .map<number, OtherMappableHKT>((a) => a + 1)
        .getValue()
)
bradzacher commented 2 years ago

IMO I don't think it's worth even talking about TS issues that aren't even planned to be implemented.

There's little reason to consider reserving syntax if TS hasn't even decided upon the syntax or if it's something they will support.

Caleb-T-Owens commented 2 years ago

The TS team accepts the idea in general, it is just not deeming it as not a priority for their current development. (see here)

If the goal of this proposal is to form a standard typing syntax for tools like typescript to make use of, isn't discussing syntax like this important? There is a good amount of references to the TS issue asking how to implement data structures that rely on higher level types or waiting for it to be resolved, so I think it should be considered

bradzacher commented 2 years ago

My point is that it's not had an official, accepted syntax proposal, nor has it had even a vague implementation timeline - it's currently a theoretical addition to the language.

The point of this proposal isn't to consider the scope of all things that might possibly be added to type languages in the future; to do so would be to define a proposal to boil the ocean. Instead the point is to define a spec for what is currently implemented in the languages as a feasible minimum set of things that should be supported.

There are TS features that are explicitly excluded from this proposal and those that are being culled after initial inclusion as well. Which is why I said that unplanned, not yet officially accepted TS features are really out of scope.

dpchamps commented 2 years ago

IMO this kind of request fits into the general request for a more permissive grammar, re: https://github.com/tc39/proposal-type-annotations/issues/103 & https://github.com/tc39/proposal-type-annotations/issues/122

The proposal (type annotations, not this issue) should be less focused on features that TypeScript supports (current and future) and more focused on a specification for static typecheckers to implement.

That way we're not constrained by the current state of typescript and TS can continue to organically grow.

trusktr commented 1 year ago

The issue

describes a more generic type syntax space, (essentially a new form of comment) where arbitrary syntax (with a few limitations) can be used inside of the comments, leaving it up to type systems like TypeScript to decide what their syntax is.