Typescript: Allow classes to be parametric in other parametric classes

Created on 19 Nov 2014  Ā·  140Comments  Ā·  Source: microsoft/TypeScript

This is a proposal for allowing generics as type parameters. It's currently possible to write specific examples of monads, but in order to write the interface that all monads satisfy, I propose writing

interface Monad<T<~>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

Similarly, it's possible to write specific examples of cartesian functors, but in order to write the interface that all cartesian functors satisfy, I propose writing

interface Cartesian<T<~>> {
  all<A>(a: Array<T<A>>): T<Array<A>>;
}

Parametric type parameters can take any number of arguments:

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

That is, when a type parameter is followed by a tilde and a natural arity, the type parameter should be allowed to be used as a generic type with the given arity in the rest of the declaration.

Just as is the case now, when implementing such an interface, the generic type parameters should be filled in:

class ArrayMonad<A> implements Monad<Array> {
  map<A, B>(f: (a:A) => B): Array<A> => Array<B> {
    return (arr: Array<A>) =>  arr.map(f);
  }
  lift<A>(a: A): Array<A> { return [a]; }
  join<A>(tta: Array<Array<A>>): Array<A> {
    return tta.reduce((prev, cur) => prev.concat(cur));
  }
}

In addition to directly allowing compositions of generic types in the arguments, I propose that typedefs also support defining generics in this way (see issue 308):

typedef Maybe<Array<~>> Composite<~> ;
class Foo implements Monad<Composite<~>> { ... }

The arities of the definition and the alias must match for the typedef to be valid.

Suggestion help wanted

Most helpful comment

with HKT's mindsets can be changed, habits broken, lost generations brought back to life, it would the biggest thing since generics and explicit nulls and undefineds, it can change everything

please consider it as a next big feature, stop listen to people who keep asking you for a better horse, give them a f*g ferrari

All 140 comments

Not to make any rash assumptions, but I believe you're typing it incorrectly. All parameter types require parameter names, so you probably meant to type

map<A, B>(f: (x: A) => B): T<A> => T<B>;

whereas right now map is a function that takes a mapper from type any (where your parameter name is A) to B.

Try using the --noImplicitAny flag for better results.

Thanks, corrected.

I've updated my comment into a proposal.

:+1: higher kinded type would be a big bonus for functional programming construct, however before that I would prefer to have correct support for higher order function and generic :p

Quasi-approved.

We like this idea a lot, but need a working implementation to try out to understand all the implications and potential edge cases. Having a sample PR that at least tackles the 80% use cases of this would be a really helpful next step.

What are people's opinions on the tilde syntax? An alternative to T~2 would be something like

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

that allows direct composition of generics instead of needing type aliases:

interface Foo<T<~,~,~>, U<~>, V<~, ~>> {
  bar<A, B, C, D>(a: A, f: (b: B) => C, d: D): T<U<A>, V<B, C>, D>;
}

It's odd to have explicit arity since we don't really do that anywhere else, so

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

is a little clearer, though, I know other languages use * in similar contexts instead of ~:

interface Foo<T<*,*>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

Though taking that point to an extreme, you might get:

interface Foo<T: (*,*) => *> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

I think T<~,~> is clearer than T~2, too. I'll modify the proposal above. I don't care whether we use ~ or *; it just can't be a JS identifier, so we can't use, say, _ . I don't see what benefit the => notation provides; all generics take some input types and return a single output type.

A lighter-weight syntax would be leaving off the arity of the generics entirely; the parser would figure it out from the first use and throw an error if the rest weren't consistent with it.

I'd be happy to start work on implementing this feature. What's the recommended forum for pestering devs about transpiler implementation details?

You can log many new issues for larger questions with more involved code samples, or make a long running issue with a series of questions as you go. Alternatively you can join the chat room here https://gitter.im/Microsoft/TypeScript and we can talk there.

@metaweta any news? If you need any help/discussion I would be glad to brainstorm on this issue. I really want this feature.

No, things at work took over what free time I had to work on it.

bump: is there a chance to see this feature ever considered?

https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-96854288 is still the current state of it. I don't see anything here that would make us change the priority of the feature.

Seems to me like this is useful in far more situations than just importing category theory abstractions. For example, it would be useful to be able to write module factories that take a Promise implementation (constructor) as an argument, e.g. a Database with a pluggable promise implementation:

interface Database<P<~> extends PromiseLike<~>> {   
    query<T>(s:string, args:any[]): P<T> 
}

:+1:

with HKT's mindsets can be changed, habits broken, lost generations brought back to life, it would the biggest thing since generics and explicit nulls and undefineds, it can change everything

please consider it as a next big feature, stop listen to people who keep asking you for a better horse, give them a f*g ferrari

Yup, Bumped to this the first 15 minutes after trying to add types to existing JS codebase. I am not switching to TS until I see it.

CanI help, actually?

I wonder how this would relate to #7848? They're very similar, although about the other facet of higher order kinds.

@boris-marinov Ryan Cavanaughā€™s reply says you can:

Having a sample PR that at least tackles the 80% use cases of this would be a really helpful next step.

Now I have time to implement such a simple PR Hope to get some hints frome core devs, but there are no questions so far - all looks good and understandable. Will track a progess here.

@Artazor Would you like to take a look at cracking #7848 as well? That takes care of the other side of this problem, involving generics, and IMHO this would feel incomplete without it (generic parameters would really simplify a lot of type-level code).

I think this proposal is absolutely wonderful. Having higher kinded types in TypeScript would take it up to a hole new level where we could describe more powerful abstractions than what is currently possible.

However, isn't there something wrong with the examples given in OP? The A in the line

class ArrayMonad<A> implements Monad<Array> {

isn't used in any of the methods, since they all have their own generic A.

Also, if implementing functor with map as a method that uses this what would it look like? Like this maybe?

interface Functor<T, A> {
  map<B>(f: (a: A) => B): T<A> => T<B>;
}

class Maybe<A> implements Functor<Maybe, A> {
  ...
}

@paldepind Check out #7848. That discussion is about that particular use case, although IMHO this and that one really needs merged into a single PR.

When does this stuff is going to land? That seems like a kind of essential.

Also will it going to make possible such:

interface SomeX<X, T> {
   ...// some complex definition
  some: X<T>
}

interface SomeA<T> extends SomeX<A, T> {
}

?

@whitecolor I think there's bigger fish to fry at the moment, which merit higher priority:

  1. TypeScript 2.0 RC was released only a little under 2 weeks ago. That'll take up a lot of time in of itself.
  2. bind, call, and apply, native JS functions, are untyped. This actually depends on the variadic generics proposal. Object.assign also needs a similar fix, but variadic generics alone won't solve that.
  3. Functions like Lodash's _.pluck, Backbone models' get and set methods, etc. are currently untyped, and fixing this basically makes Backbone usable with TypeScript in a much safer way. It also may have implications for React in the future.

Not that I don't want this feature (I would _love_ for such a feature), I just don't see it as likely coming soon.

@isiahmeadows
Thanks for explanation. Yeah 3rd item in the list is very important, waiting for https://github.com/Microsoft/TypeScript/issues/1295 too.

But I hope for current issue maybe in 2.1dev somehow.

I agree. Hopefully it can make it in.

(Rank 2 polymorphism, which this issue wants, is also a necessity for
Fantasy Land users, to properly type the various ADTs within that spec.
Ramda is a good example of a library that needs this fairly badly.)

On Tue, Sep 6, 2016, 11:00 Alex [email protected] wrote:

@isiahmeadows https://github.com/isiahmeadows
Thanks for explanation. Yeah 3rd item in the list is very important,
waiting for #1295 https://github.com/Microsoft/TypeScript/issues/1295
too.

But I hope for current issue maybe in 2.1dev somehow.

ā€”
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-244978475,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBMvxBALBe0aaLOp03vEvEyokvxpyks5qnX_8gaJpZM4C99VY
.

Seems that this feature would helps us a lot to define react forms. For example, you have struct:

interface Model {
  field1: string,
  field2: number,
  field3?: Model
}

I have a handler, which defined as:

interface Handler<T> {
  readonly value: T;
  onChange: (newValue: T) => void;
}

this handler passed as a props to React components. Also I have a function, which takes struct and returns same struct, but with Handlers instead of values:

function makeForm(value: Model): {
  field1: Handler<string>,
  field2: Handler<number>,
  field3: Handler<Model>,
}

As for now I can't type that function properly, because TS can't produce type based on structure of other type.

Cow I could type makeForm with HKT?

Hm, interesting.

Maybe something like this may be possible:

//Just a container
interface Id <A> {
  value: A
}

interface Model <T> {
  field1: T<string>,
  field2: T<number>,
  field3?: T<Model>
}

makeForm (Model<Id>): Model<Handler>

@boris-marinov The most interesting point is this line:

interface Model<T> {
  //...
  field3?: T<Model> // <- Model itself is generic.
                    // Normally typescript will error here, requiring generic type parameter.
}

might be worth mentioning that HKT could have been an answer to so called partial types (https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155):

type MyDataProto<K<~>> = {
    one: K<number>;
    another: K<string>;
    yetAnother: K<boolean>;
}
type Identical<a> = a;
type Optional<a> = a?; // or should i say: a | undefined;
type GettableAndSettable<a> = { get(): a; set(value: a): void }

type MyData = MyDataProto<Identical>; // the basic type itself
type MyDataPartial = MyDataProto<Optional>; // "partial" type or whatever you call it
type MyDataProxy = MyDataProto<GettableAndSettable>; // a proxy type over MyData
// ... etc

Not quite. {x: number?} isn't assignable to {x?: number}, because one
is guaranteed to exist, while the other isn't.

On Tue, Oct 11, 2016, 09:16 Aleksey Bykov [email protected] wrote:

might be worth mentioning that HKT could have been an answer to so called
partial types (#4889 (comment)
https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155
):

type MyDataProto one: K;
another: K;
yetAnother: K;
}type Identical = a;type Optional = a?; // or should i say: a |
undefined;type GettableAndSettable
= { get(): a; set(value: a):
void }
type MyData = MyDataProto; // the basic type itselftype
MyDataPartial = MyDataProto; // "partial" type or whatever
you call ittype MyDataProxy = MyDataProto; // a
proxy type over MyData// ... etc

ā€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-252913109,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBNFYFfiW01MT99xv7UE2skQ3qiPMks5qy4wRgaJpZM4C99VY
.

@isiahmeadows you are right, at the moment there is no way/syntax to make a property truely optional based solely on its type, and thats a shame

Yet another one: it would be good if property can be made readonly. Seems some kind of macros feature required.

Just throwing this out there... I prefer the * syntax over the ~ syntax. Something about ~ just seems so far out of the way from a keyboard layout perspective. Also, I'm not sure why, but I think * seems a bit more readable/distinguishable with all the angle brackets that are in the mix. Not to mention, people familiar with other languages like Haskell might immediately associate the syntax to HKT. Seems a bit more natural.

I'd have to agree with the * syntax. First, it is more distinguishable,
and second, it better represents an "any type works" type.


Isiah Meadows
[email protected]

On Sun, Nov 6, 2016 at 12:10 AM, Landon Poch [email protected]
wrote:

Just throwing this out there... I prefer the * syntax over the ~ syntax.
Something about ~ just seems so far out of the way from a keyboard layout
perspective. Also, I'm not sure why, but I think * seems a bit more
readable/distinguishable with all the angle brackets that are in the mix.
Not to mention, people familiar with other languages like Haskell might
immediately associate the syntax to HKT. Seems a bit more natural.

ā€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-258659277,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBHQ4SYeIiptB8lhxEAJGOYaxwCkiks5q7VMvgaJpZM4C99VY
.

Milestone: community? What is current state of this issue/feature?

@whitecolor the status is DIY (do it yourself)

The issue has Accepting PRs label. this means that pull requests to implement this feature are welcomed. See https://github.com/Microsoft/TypeScript/wiki/FAQ#what-do-the-labels-on-these-issues-mean for more details.

Also please see https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-96854288

Ok, I see the labels, just have doubts that if non-TS team is capable of accomplishing it.

Now I have time to implement such a simple PR Hope to get some hints frome core devs, but there are no questions so far - all looks good and understandable. Will track a progess here.

@Artazor Do you have any luck with this?

@raveclassic - it turned to be more difficult than it seemed, however I still hope to move forward. Syntactically it is obvious, but the typechecking rules/phases are not as clear to me as I want -)

Lets try to revive my activity -)

Just tracking a progress, and the path of the idea development. I've considered three options how to implement this feature.

I've planned to enrich a TypeParameterDeclaration with optional higherShape property

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        higherShape?: HigherShape // For Higher-Kinded Types <--- this one 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

and have considered three options how HigherShape could beimplemented

1. Simple Arity for the Domain

type HigherShape = number

it corresponds to the following usage:

class Demo<Wrap<*>, WrapTwo<*,*>> {   // 1 and 2
    str: Wrap<string>;
    num: Wrap<number>;
    both: WrapTwo<number, string>;
}

in this simplest case, looks like that the number type would be sufficient. Nevertheless, we should be able to determine an actual higherShape for every given type to be sure we can use it as a type argument for the specific shape requirements. And here we're facing a problem: the higher shape of the Demo class itself is not expressible as a number. If it would, then it should be represented as 2 - since it has two type parameters,
and it would be possible to write

var x: Demo<Array, Demo>

and then battling with the deferred type-checking problem with property .both. Thus the number type is not sufficient (i believe);

in fact type Demo has the following high order shape:

(* => *, (*,*) => *) => *

2. Fully structured Domain and Co-Domain

Then I've investigated the opposite, most full representation of the higher shapes, that would allow representing such shapes as aforementioned one, and even worse:

(* => (*,*)) => ((*,*) => *)

The data structure for this is straightforward, but it does not interplay well with the TypeScript type system. If we would allow such higher-order types then we will never know whether * means the ground type, that could be used for the typing of values. Besides, I even did not manage to find an appropriate syntax how to express such a monstrous higher order constraints.

3. Structured Domain / Implicit Simple Co-Domain

The main idea - type expression (even with actual type arguments) always results in a ground type - that can be used to type a variable. On the other hand, each type parameter can have its own detailed type parameters in the same format that is used elsewhere.

This was my final decision that I would try to advocate.

type HigherShape = NodeArray<TypeParameterDeclaration>;

example:

class A {x: number}
class A2 extends A { y: number }
class Z<T> { z: T; }

class SomeClass<T1<M extends A> extends Z<M>, T2<*,*<*>>, T3<* extends string>> {
        var a: T1<A2>; // checked early
        var b: T2<string, T1>; // second argument of T2 should be generic with one type parameter  
        var c: T3<"A"|"B">; // not very clever but it is checked
        // ...
        test() {
             this.a.z.y = 123 // OK
             // nothing meaningful can be done with this.b and this.c
        }
}

Here I want to note, that M is local for T1<M extends A> extends Z<M> and exists in a deeper visibility scope than T1. Thus M is not available in the SomeClass body.
And * means simply a fresh identifier (anonymous type) that never clash with anything (and could be implemented at later stage)


Thus the final signature of the TypeParameterDeclaration

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        typeParameters?: NodeArray<TypeParameterDeclaration> // !!! 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

Want to hear any opinion of @DanielRosenwasser, @aleksey-bykov, @isiahmeadows and others -)

Sounds okay to me, but I know very little about the internal structure of TypeScript's code base.

Would like to add my voice to the choir requesting this and to cheer you on, Artazor! :)

This feature would be useful to me in my implementation of making Redux type-safe.

@michaeltontchev What issues are you having making Redux typesafe?

In case you're interested, I recently published https://github.com/bcherny/tdux and https://github.com/bcherny/typed-rx-emitter, which build on ideas from Redux and EventEmitter.

Now looks, need to rebase to the @rbuckton branch #13487 with default generic parameters. In other case we will conflict largely.

@bcherny - thanks for the links, I'll check them out!

I was looking at how to make combineReducers type-safe by ensuring it has a reducer of the right type for every property of the state (with no extras). I managed to do it in this specific case without nested generics, but a nested solution would have been nicer. I have the following:

import { combineReducers, Reducer } from 'redux';

interface IState {
    // my global state definition
}

type StatePropertyNameAndTypeAwareReducer\<S> = {
    [P in keyof S]: Reducer<S[P]>;
};

let statePropertyToReducerMap : StatePropertyNameAndTypeAwareReducer<IState> = {
    navBarSelection: navBarReducer,
};

let combinedReducers = combineReducers<IState>(statePropertyToReducerMap);

Basically, the type I introduce above guarantees that the reducer mappings you pass to combineReducers cover every property of your state and have the proper return types. I wasn't able to find any such solution while searching online - seems to me like it can't be done without the keyof feature, which came out only two months ago :)

I expect the keyof feature will come handy for ImmutableJs as well to make setters and getters type-safe, though you still might need some additional tooling around that.

Edit: to clarify, nested generics would have allowed me to not have to hardcode the Reducer type in the StatePropertyNameAndTypeAwareReducer type, but instead to pass it in as a generic, but it would need to be a nested generic, which isn't possible at the moment.

Edit2: created an issue for Redux here: https://github.com/reactjs/redux/issues/2238 Seems like they don't do much TypeScript, so they're looking for TypeScript people who know Redux to weigh in.

How is it going?

Maybe a naive question, but why ~ or * instead of a regular generic parameter? Is it to indicate that it's unused? Ie. why not:

type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

Or:

kind Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

Or even:

abstract type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

@bcherny I believe this causes ambiguities in the syntax, since Functor<A<T>> would formerly have meant "A of T", where T is some type in local scope. It is unlikely, but this syntax might also end up being a breaking change for some codebases, for the same reason.

@masaeedu I see. The new syntax means "bind T lazily", rather than "bind T strictly in the current scope".

That said, I think @DanielRosenwasser's proposal for T: * => * makes the most sense here, since there is "prior art" for it.

In Haskell, the operator -> is actually a parametrized type (perhaps it is easier to visualize Func<TArg, TRet>). The type constructor -> accepts two arbitrary types T and U and produces the type of a value constructor (i.e. function) that maps values of type T to values of type U.

The interesting thing is that it is also a kind constructor! The kind constructor -> accepts two arbitrary kinds T* and U* (asterisk just for visual distinction), and produces the kind of a type constructor that maps types of kind T* to types of kind U*.

You might notice a pattern at this point. The syntax and semantics being used to define and refer to types is simply being reused to define and refer to kinds. In fact, it is not even being reused, it is just implicitly defining things in two different universes at once. (the fact that it is isomorphic in fact means that this is capable of defining things in infinite levels, values -> types -> kinds -> sorts -> ..., except for the unfortunate *, but that's a topic for a different time)

In fact, this pattern makes so much sense that some people implemented a widely used GHCi extension that generalizes it to all type constructors, not just ->. The extension is called "data kinds", and it is how Haskell comes by it's heterogeneous lists ([] is both the type of lists of values, and the kind of lists of types), heterogeneous tuples, "smart length" vectors, and many other features besides.

Perhaps we don't want to go as far as DataKinds just yet, so we will just stick with the kind constructors * and ->, but if we follow Daniel's proposed syntax, or more generally make kind definitions isomorphic to type definitions, we open ourselves up to take advantage of future development in this area.

Following on from my previous rambling post, I'd like to recommend that we use any instead of *; this represents both the type of every value and the kind of every type. If the syntax appears confusing, we could take a page out of Haskell's book and use an ' prefix to disambiguate kinds and types.

OP's example would then be written like so:

interface Monad<(T: 'any => 'any)> {
    // ...
}

Nitpick: I find any confusing in general in the sense it does two different things.
It's a super type of all others, like never is a sub type of all others, so if a function asks for an any parameter, you can put in whatever. So far so good.
The part where it gets funny is when a function asks for something specific, and you provide any. This type-checks, while every other type that's broader than what was asked for would make it error instead.
But yeah, whatever.

On another note ' would be confusing since it is also used in string literals.

@Artazor Any news with this? Last time you mentioned you need to rebase on default generic params. And it sounds to me you are the only one close enough to a working POC.

It is also worth thinking about how this interacts with subtyping. Using * by itself is not sufficient; in languages that use ad-hoc polymorphism instead of bounded polymorphism, you have constraint kinds for limiting acceptable type arguments. As an example, the kind of Monad T is actually Constraint, not *.

In TypeScript we use structural subtyping instead, so our kinds need to reflect subtype relations between types. The Scala paper on this subject might yield some good ideas on how to represent variance and subtype relations in a kind system: "Towards Equal Rights for Higher Kinded Types".

Any progress on this?

An alternative approach by @gcanti https://medium.com/@gcanti/higher-kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

The problem with the approach taken by fp-ts is that it makes you re-implement otherwise proven libraries. To me, the idea of typescript is to be able to correctly type what is currently thought as best practices in JavaScript, not to force you to reimplement it in a ts way.

There are a lot of examples here that show that HKT are needed to correctly describe the contracts we currently use in js libs, either fantasy land, ramda or react forms.

It would be really nice to see this impemented :)

~Is there anyone willing/capable to work on this paid? Feel free to contact me to discuss. Or anyone is able to coach someone we might find to work on this, please also let me know.~[EDIT: Iā€™ve [probably decided](https://github.com/keean/zenscript/issues/35#issuecomment-357567767) to abandon this ecosystem and my subsequent comment in this thread caused me to realize it would probably be a massive undertaking]

An alternative approach by @gcanti https://medium.com/@gcanti/higher-kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

I didn't bother to fully grok that, because I observe the resultant map still explicitly specifies the Option container type and thus isn't fully generic in way that higher-kinded types (HKT) can provide:

function map<A, B>(f: (a: A) => B, fa: HKTOption<A>): Option<B> {
  return (fa as Option<A>).map(f)
}

As @spion noted on Aug. 26, 2016, HKT are necessary for making generic any function that needs a factory and in which the type parametrized container type is to be itself generic. We had explored this in our discussions about programming language design.

P.S. If you're curious, this feature factors significantly into my (including @keean's) analysis of the programming language landscape. I realize our broader scope programming language design interests don't entirely correlate to the priorities of Typescript, given its primary goal to be a superset of Javascript/ECMAScript and prioritize support of that ecosystem It seems though based on the upthread comments, that there is significant demand for HKT in this ecosystem.

@shelby3 FWIW Option's map (in Option.ts) is not generic because represents the instance while Functor's map (in Functor.ts) is generic because represents the type class. Then you can define a generic lift function that can operate with any functor instance.

It would be really nice to see this impemented :)

I strongly agree :)

@shelby3: to get a feature like this merged, your best bet might be to get
them to prioritize it on the TS roadmap; I had a few PRs that primarily got
feedback/merges either when small fixes or if they were already looking
into them. I don't wanna be negative but it's a consideration if you're
about to invest resources into this.

On Jan 8, 2018 4:05 PM, "shelby3" notifications@github.com wrote:

Is there anyone willing/capable to work on this paid? Feel free to contact
me shelby@coolpage.com to discuss. Or anyone is able to coach someone
we might find to work on this, please also let me know.

An alternative approach by @gcanti https://github.com/gcanti
https://medium.com/@gcanti/higher-kinded-types-in-
typescript-static-and-fantasy-land-d41c361d0dbe

I didn't bother to fully grok that, because I observe the resultant map
still explicitly specifies the Option container type and thus isn't fully
generic in way that higher-kinded types (HKT) can provide:

function map(f: (a: A) => B, fa: HKTOption): Option {
return (fa as Option
).map(f)
}

HKT are necessary for making generic any function that needs a factory and
in which the type parametrized container type is to be itself generic. We had
explored this
https://github.com/keean/zenscript/issues/10 in our
discussions about programming language design.

P.S. If you're curious, this feature factors significantly into my
(including @keean https://github.com/keean's) analysis of the
programming language landscape
https://github.com/keean/zenscript/issues/35#issuecomment-355850515. I
realize our views don't entirely correlate to the priorities of Typescript
with the primary goal to be a superset of Javascript/ECMAScript and support
that ecosystem.

ā€”
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-355990644,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AC6uxYOZ0a8G86rUjxvDaO5qIWiq55-Fks5tIi7GgaJpZM4C99VY
.

@gcaniti apologies for the noise and thank you for the additional explanation. I should have studied more before commenting. Of course, it's my error in conceptualization because (I know already) a functor requires an instance implementation.

Afaics, your clever "hack" enables referring to a factory generically (e.g. lift), but requires the additional boilerplate of Module Augmentation to (update and) specialize each typing of the generic factory for the specialized type of the functor, e.g. Option. Wouldn't that boilerplate be required for every generic use of a generic factory, e.g. the generic sort example @keean and I discussed? Possibly there may be other corner cases to be discovered as well?

Did Kotlin copy your idea or vice versa? (some additional criticisms at that link but I don't know if they apply to the Typescript case)

I don't wanna be negative but it's a consideration if you're about to invest resources into this.

Yeah that thought also occurred to me. Thanks for articulating it. I suspect one of the considerations would be the impacts generally on the type system and any corner cases it might generate, as pointed out by @masaeedu. Perhaps there would be resistance unless this was very thoroughly thought out and demonstrated.

Note I'm also looking into Ceylon to better ascertain what their level of investment into the EMCAScript compile target will be. They appear to have experimental support for HKT (apparently only for the JavaScript target?), but the modules integration may be lacking? (I need to study more).

I've also just been bitten by this limitation. I'd like I in the following example to be automatically inferred:


interface IdType<T> {
  id: T;
}

interface User {
  id: number;
  name: string;
}

function doStuff<T extends IdType<I>>() {
  const recs = new Map<I, T>();
  return {
    upsert(rec: T) {
      recs.set(rec.id, rec);
    },
    find(id: I) {
      return recs.get(id);
    },
  };
}

(function () {
  const stuff = doStuff<User>();
  stuff.upsert({id: 2, name: "greg"});
  console.log(stuff.find(2));
})();

As far as I can tell, this requires a higher-kinded type or else specifying a duplicate generic parameter (e.g. doStuff<User, number>()) which seems redundant.

I've recently been struck by this limitation as well.

I've been working on a library for promises. It provides various utility functions for working with them.

A central feature of the library is that it returns the same type of promise you put inside it. So if you were using a Bluebird promise and called one of the functions, it would return a Bluebird promise with all the additional functionality they provide.

I wanted to encode this in the type system, but I quickly realized that this requires working with a type P of kind * -> * such that P<T> extends Promise<T>.

Here is an example of one such function:

/**
* Returns a promise that waits for `this` to finish for an amount of time depending on the type of `deadline`.
* If `this` does not finish on time, `onTimeout` will be called. The returned promise will then behave like the promise returned by `onTimeout`.
* If `onTimeout` is not provided, the returned promise will reject with an Error.
*
* Note that any code already waiting on `this` to finish will still be waiting. The timeout only affects the returned promise.
* @param deadline If a number, the time to wait in milliseconds. If a date, the date to wait for.
* @param {() => Promise<*>} onTimeout Called to produce an alternative promise once `this` expires. Can be an async function.
*/
timeout(deadline : number | Date, onTimeout ?: () => PromiseLike<T>) : this;

In the above situation I was able to avoid needing higher kinds by use of the rather hacky this type.

However, the following case cannot be solved:

/**
* Returns a promise that will await `this` and all the promises in `others` to resolve and yield their results in an array.
* If a promise rejects, the returned promise will rejection with the reason of the first rejection.
* @param {Promise<*>} others The other promises that must be resolved with this one.
* @returns {Promise<*[]>} The return type is meant to be `Self<T[]>`, where `Self` is the promise type.
*/
and(...others : PromiseLike<T>[]) : ExtendedPromise<T[]>;

Because there is no hack that allows me to do this<T[]> or something.

Note my small apology in the documentation.

Got anther scenario where I believe this feature would be useful (assuming I've interpreted the proposal right), as indicated by the above reference.

In the package in question, it is necessary to have an untyped generic class or function used as a type, as the generic types are typically user created.

Applying the proposal to my scenario, I believe it would look something like:

import { Component, FunctionalComponent } from 'preact';

interface IAsyncRouteProps {
    component?: Component<~,~> | FunctionalComponent<~>;
    getComponent?: (
        this: AsyncRoute,
        url: string,
        callback: (component: Component<~,~> | FunctionalComponent<~>) => void,
        props: any
    ) => Promise<any> | void;
    loading?: () => JSX.Element;
}

export default class AsyncRoute extends Component<IAsyncRouteProps, {}> {
    public render(): JSX.Element | null;
}

Given that there is no way to reference the generic types reliably in my implementation, I'm sure I've missed something.

@Silic0nS0ldier Actually that case can be solved right now. You use a structural constructor type, like this:

type ComponentConstructor = {
    new<A, B>() : Component<A, B>;
}

And then say, component ?: ComponentConstructor.

Even more generally, you can actually have a generic function type:

let f : <T>(x : T) => T

This is called rank-n parameteric polymorphism and is actually a pretty rare feature in languages. So it's even more puzzling as to why TypeScript doesn't have higher-kinded types, which is a much more common feature.

The limitation discussed here will appear if you need to reference a specific TComponent<T, S>. But in your case this seems unnecessary.


You can also use typeof Component which will get you the type of the constructor Component but this will cause various issues with subtypes.

@GregRos Your proposed solution looked promising (it accepts the types inside the definition file), but compatible types are being rejected. https://gist.github.com/Silic0nS0ldier/3c379367b5e6b1abd76e4a41d1be8217

@Silic0nS0ldier See my comments on the gist.

@chrisdavies Does this work?

interface IdType<T> {
    id: T;
}

interface User {
    id: number;
    name: string;
}

function doStuff<T extends IdType<any>>() {
    type I = T['id']; // <==== Infer I
    const recs = new Map<I, T>();
    return {
        upsert(rec: T) {
            recs.set(rec.id, rec);
        },
        find(id: I) {
            return recs.get(id);
        },
    };
}

(function() {
    const stuff = doStuff<User>();
    stuff.upsert({ id: 2, name: "greg" });
    console.log(stuff.find(2));
})();

@jack-williams Yep. That works for my scenario. I had not found that example in the docs (though I'm known for missing things!). Thank you!

I've been thinking about this feature a lot, and I have some thoughts on the matter, leaning towards some kind of specification, but I can still see lots of issues. My suggestions are a bit different from what has been proposed so far.


First of all, I think using any kind of T<*, *> syntax for type constructors is a bad idea because it doesn't scale well with the complexity of the type constructor. I'm also not sure if there's a point in specifying the kind of the type constructor whenever it is referenced, since we don't do this for functions, even for functions with type parameters.

I think the best way to implement this is to treat higher-kinded types like other types, with regular names, and define a good subtype relation over type constructors themselves that can be used to impose constraints.

I do think we should use some kind of prefix or postfix to distinguish them from other types, mainly to protect users from incomprehensible error messages involving type constructors when they've only been wanting to write regular code. I kind of like the look of: ~Type, ^Type, &Type or something like that.

So for example, a function signature might be:

interface List<T> {
    push(x : T);
}

function mapList<~L extends ~List, A, B>(list : L<A>, f : (x : A) => B) : L<B>;

(I'm not using the ~ prefix for the constructed types on purpose)

By using extends here I've basically said two things:

**1. If it's necessary: ~L is a type constructor that has the same kind as ~List, i.e. the kind * -> * (or maybe * => *, since => is the TypeScript arrow).

  1. ~L is a subtype of ~List.**

Using extends to denote the kind of a type constructor scales to arbitrarily complex type constructors, including things like ((* => *) => (* => *)) => *.

You can't really see that kind in the type's identifier, but I don't think you need to. I'm not even sure a subtype relation between type constructors has to preserve kinds, so (1) might not be necessary.

No constructing incomplete types

I think we shouldn't support constructing incomplete types. That is, something like this:

(*, *) => * => *

I think it would create more trouble than it's worth. i.e. every type constructor must construct some concrete type, and that concrete type must be specified in the context where the TC is defined.

Structural way of defining type constructors

I also think there should be a structural way of specifying type constructors, in the same way any other type can be specified structurally, including very high-order generic function types. I've been thinking of syntax such as:

~<A, B> { 
    a : A,
    b : B
}

Which is similar to the existing syntax for function types with type parameters:

<A, B>() => { a : A, b : B};

The two can even be combined to get this:

~<A><B, C> => [A, B, C]

Which is a type constructor constructing a generic function type.

The benefit is that these structural types can be used when specifying other structural types, when specifying type constraints, and so on. Sometimes this means they can use reference local symbols that can't be referenced from anywhere else.

Here is an example:

type List<A, B> = ...;

type AdvancedType<~L extends ~<A>List<A, B>, B> = ...;

In the above example, the structural type constructor ~<A>List<A, B> references the type parameter B. It's not possible to specify this relationship in any other way, at least without encoding the partially constructed type List<A, *>. There are other examples too.

The subtype relation

The subtype relation seems to make sense, but I've run into a number of difficulties trying to characterize it.

My first idea was the following. For ~A to be a subtype of ~B:

  1. (a) They must have the same kind (in terms of arity, not constraints).
  2. (b) For every legal parameterization Tā‚, Tā‚‚, ... of ~A, A<Tā‚, Tā‚‚, ...> must be a subtype of B<Tā‚, Tā‚‚, ...>.

However, this has several limitations.

  1. class MySpecialPromise implements PromiseLike { }
    In this case, ~MySpecialPromise is not a subtype of ~PromiseLike because they have different kinds.

  2. class MyArrayPromise implements PromiseLike

    The subtype relation isn't conserved in this case either.

A more generalized version of (b) is the following:

(b) For every legal parameterization Tā‚, Tā‚‚, ... of ~A, there exists a parameterization Sā‚, Sā‚‚, ... of ~B such that A<Tā‚, Tā‚‚, ...> is a subtype of B<Sā‚, Sā‚‚, ...>.

In other words, there is a mapping F(Tā‚, Tā‚‚, ...) = Sā‚, Sā‚‚, ... with the above properties. This mapping must be used in order to construct the parameterized B<...> from a parameterized A<...>. It may allow us to do this even if the type constructors have different kinds.

The problem with this relation is that I'm not sure how it would be possible to find the correct mapping. In languages with nominal typing, every statement along the lines of:

A<...> extends B<...>

Defines a mapping between the type parameters of ~A and the type parameters of ~B, so this is how the mapping may be recovered. However, in TypeScript's sytstem of structural typing, we cannot rely on explicit statements of this type.

One way is to only support type constructors for types with the right type informaiton, like implements clauses or some sort of abstract type member similar to Scala. I'm not sure if this is the way forward, though.

@GregRos - Interesting Notes! A few questions.


What do you mean by concrete type? Do you mean something with kind *, or a type with no bound type parameters?


No constructing incomplete types
I think we shouldn't support constructing incomplete types. That is, something like this:
(*, *) => * => *

What do you mean by constructing incomplete types? Do you mean that every application like L<A> should have kind *. Is the pair kind constructor special in your example, for instance, would (* => *) => * => * be ok?


Structural way of defining type constructors

~<A, B> { 
    a : A,
    b : B
}
inferface TyCon<A, B> { 
    a : A,
    b : B
}

Are these examples different, other than the first being anonymous?


The subtype relation

~A and ~B don't refer to types so does it make sense for them to have a subtype relation? When do you actually need to check that one constructor is a 'subtype' of another? Is it possible to wait until the constructors are applied and check the resulting types?

@jack-williams Thanks for the feedback!

What do you mean by constructing incomplete types? Do you mean that every application like L<A> should have kind *. Is the pair kind constructor special in your example, for instance, would (* => *) => * => * be ok?

Yes, exactly. Every application like L<A> should have kind *. I'm not sure how sold I am on that.


Are these examples different, other than the first being anonymous?

The first is a type expression, while the second is a declaration. They are identical in most respects, in the same way that these types are identical in most respects:

{
     a : number;
     b : string;
}

interface Blah {
    a : number;
    b : string;
}

The syntax has several motivations:

  1. Just like everything else in TypeScript, it lets type constructors be specified structurally and anonymously.
  2. A type expression (like the anonymous object typed mentioned above) may be used in certain contexts where declaration statements cannot be used, such as in the type signatures of functions. This allows them to capture local identifiers and express things that cannot be expressed otherwise.

~A and ~B don't refer to types so does it make sense for them to have a subtype relation? When do you actually need to check that one constructor is a 'subtype' of another? Is it possible to wait until the constructors are applied and check the resulting types?

Type constructors may or may not be regarded as types. I propose to regard them as types, just incomplete ones that have no values and cannot appear in any context that requires the type of a value. This is the same philosophy taken by Scala, in this document

By a subtype relation, I essentially mean some sort of "conformity" relation that can be used to constrain type constructors. For example, if I want to write a function that works on all sorts of promises of various types, like Promise<T>, Bluebird<T>, and so on, I need the ability to constrain TC parameters with the interface PromiseLike<T> in some way.

The natural word for this type of relation is a subtype relation.

Let's look at an example. Assuming we've worked out a subtype relation between type constructors, I can write a function like this:

function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B>;

And the constraint ~P extends ~PromiseLike is supposed to guarantee this is a function that works on promises, and only promises. The constraint will also guarantee that inside the body of the function, promise will be known to implement PromiseLike<A>, and so on. After all, the members recognized by TypeScript in the body of the function are precisely those that can be proven to exist through constraints.

In the same way Promise<T> extends PromiseLike<T>, because they are structurally compatible and may be replaced with one another, ~Promise extends ~PromiseLike because they construct structurally compatible types and thus can be replaced with one another.


To underline the problem with the subtype issue, consider once again:

interface MyPromise<T> extends Promise<T[]> {}

Can we abstract over ~MyPromise in the same way we abstract over ~Promise? How do we capture the relationship between them?

The mapping I talked about earlier is the mapping that, given a parameterization of ~MyPromise, will produce a parameterization of ~Promise so that the type constructed by ~MyPromise is a subtype of the one constructed by ~Promise.

In this case, the mapping is like this:

T => T[]

@GregRos

In this case, ~MySpecialPromise is not a subtype of ~PromiseLike because they have different kinds.

In Haskell, this kind of problem is solved by allowing partial application of types, and defining types so the final type parameter coincides with the type parameter of whatever interface you're implementing.

In your example, MySpecialPromise would be defined as MySpecialPromise<TSpecial, TPromiseVal>, and ~MySpecialPromise<SpecialType> would have identical kind to ~Promise.

@GregRos

By a subtype relation, I essentially mean some sort of "conformity" relation that can be used to constrain type constructors. For example, if I want to write a function that works on all sorts of promises of various types, like Promise, Bluebird, and so on, I need the ability to constrain TC parameters with the interface PromiseLike in some way.
function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B>;

I think when it comes to type-checking that function you would try and unify BlueBird<T> and PromiseLike<T> for the chosen T, these are just concrete types and fall under subtyping. I don't see why you would need a special relation for the constructors ~BlueBird and ~PromiseLike.

I guess it would be used in something like this?


let x: <P extends ~PromiseLike>(input : P<A>, func : (x : A) => B) : P<B>;
let y: <P extends ~BlueBird>(input : P<A>, func : (x : A) => B) : P<B>;
x = y;

Here you might want to check that the constraints of y imply the constraints of x, but does TypeScript not already have machinery to check that the BlueBird<T> extends PromiseLike<T> that could be used?

@jack-williams It comes down to how you specify the following constraint:

~P is a type constructor such that, for all A, P<A> is a subtype of PromiseLike<A>.

What kind of syntax would you use? What kind of concept would you use? You can write something like this:

function mapPromise<~P, A, B where P<A> extends PromiseLike<A>>

But this syntax has limitations. For example, you can't express this class at all, because we can't construct the type P<A> at the point where it's declared in order to constrain it:

class PromiseCreator<~P extends ~PromiseLike> {
    create<A>() : P<A>;
}

But I guess you can use existential types for that, like this:

//Here A is not a captured type parameter
//It's an existential type we introduce to constrain ~P
class PromiseCreator<~P with some A where P<A> extends PromiseLike<A>> {
    create<A>() : P<A>;
}

Then you can require all type constructors to be constrained via their constructed types within the signature of the function or type, optionally using existential types.

With existential types, this would have the same expressive power as a subtype relation with a mapping.

However, this would have multiple issues:

  1. Specifying type constructors with kinds such as ((* => *) => *) => * would require introducing many existential types, some of which would have to be higher-order. All of them would have to sppear in the signature of the function or class.
  2. I'm not entirely sure it would be easier to find the existential types in question than finding the mapping.
  3. I think it's less elegant than the subtype relation.
  4. Potentially introduces another form of type that you'd have to deal with.

@GregRos

What kind of syntax would you use? What kind of concept would you use?

_Personally_ I wouldn't use any special syntax and just use:

function mapPromise<P extends PromiseLike, A, B>(p: P<A>, f: (x: A) => B): P<B>

class PromiseCreator<P extends PromiseLike> {
    create<A>() : P<A>;
}

but this is just my opinion as I view things like number as a null-ary constructor: so there doesn't need to be a distinction.

My view on subtyping for constructor functions would be to keep it as simple as possible. They should have the same arity and the parameters should be subkinds of eachother, taking into account contravariance and covariance much like the Scala paper.

Partial application can get round the cases where they have different arity (I wouldn't mind auto-curryng for type constructors so you can just write MySpecialPromise<SpecialType>).

In the example interface MyPromise<T> extends Promise<T[]> {} I'd have to be honest and say I'm not convinced that it's worth the complexity to handle this case -- I think it would be a useful enough feature without it.

Handling that case is equivalent to (I think), saying: ~MyPromise extends ~(Promise . []) where [] is the list constructor and . is constructor composition. This seems like things get much harder as now it's not enough just to inspect the structure of constructors, but you have to reason about composition too!

@jack-williams This doesn't work with default type parameters. If I write P extends Foo, where Foo has a default type parameter, i.e. type Foo<T = {}> = ..., then what is the kind of P?

I would just like to say that I approve of higher-order types (I've had situations in real TypeScript projects where they would be useful).

However I don't think they should support currying. I love Haskell, but that just doesn't fit in with TypeScript.

Higher-order types are useful even without currying or partial application, but if partial application is needed I would prefer to see explicit syntax for it. Something like this:

Foo<number, _>  // equivalent to `type Foo1<A> = Foo<number, A>`

@cameron-martin

Edit: Sorry, I don't think my comments were _not_ very clear. By P has its own kind I mean that it has a kind imposed on it by its use. Say constraints are always assumed to be the highest kind, so Foo is assumed to be ~Foo. Only if we force P to be a lower kind do we check if Foo has a default parameter. My concern with this is kind inference, but in that case ~ wont help and I think we need full annotations.

P has it's own kind, does it not? Would the question not be do we treat Foo as ~Foo, or as Foo<{}>: I would argue that would be driven by the kind of P. So if P is a type we force the default parameter, and if P is a constructor * => *, then we treat Foo the same.

@Pauan Agree with your suggestions.

@jack-williams I've considered that notion of subtyping, as I mentioned earlier:

My first idea was the following. For ~A to be a subtype of ~B:

  1. (a) They must have the same kind (in terms of arity, not constraints).
  2. (b) For every legal parameterization Tā‚, Tā‚‚, ... of ~A, A<Tā‚, Tā‚‚, ...> must be a subtype of B<Tā‚, Tā‚‚, ...>.

The problem is that if we keep things as simple as possible, we'd end up with a subtype relation that's paradoxical and doesn't fit in the language.

If MyPromise<T> extends Promise<T[]> that means MyPromise<T> has to be useable wherever Promise<T[]> is useable, but this would no longer be the case.

If you used as to convert a a : MyPromise<T> to Promise<T[]>, you would be upcasting, but this would paradoxically make a more assignable.

Existing generic constraints, which do follow the existing subtype relation, can also be used to achieve a similar effect and cause strange behavior:

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

Typing would become at least partially nominal as a side-effect too. These types would suddenly be different, where they are currently the same:

type GenericNumber<T> = number;

type RegularNumber = number;

I'm not even sure what effect it would have on complex union / intersection types with type parameters, purely structural types, types with member comprehensions, and the like.

My personal feeling is that: The subtype relation over type constructors needs to respect the existing one, not oppose it. Sadly, this requires things to be more complex.


The biggest reason for using some kind of special notation for type constructors is that 99% of developers don't know what a type constructor is and wouldn't want to be bombarded with error messages about them.

This is very different from Haskell, where every developer is required by law to take an advanced course in category theory.

A secondary reasons is that in some cases (like the aforementioned default parameters case), syntax would either be ambiguous or else it wouldn't be possible to abstract over a specific type constructor at all.

EDIT: Sorry @GregRos I didn't see your latter comments!

The subtype relation over type constructors needs to respect the existing one, not oppose it.

If this can be achieved then I agree. I just haven't got my head around all the details and how easy this would be.

A secondary reasons is that in some cases (like the aforementioned default parameters case), syntax would either be ambiguous or else it wouldn't be possible to abstract over a specific type constructor at all.

I'm not sure I agree that it would be ambiguous if you always assume the highest kind of the constraint until you need it to be lower. This isn't an assertion and if there are other examples that show otherwise then fair enough.


The problem is that if we keep things as simple as possible, we'd end up with a subtype relation that's paradoxical and doesn't fit in the language.

That might be true, I guess I'm just concerned as to whether the alternative is possible to actually implement. For what it's worth, if the more complex solution works that would be great!

Having the more general notion of subtyping that shows the existence of a mapping function seems hard to implement in general. Is my following example interpreting your rules correctly?

(b) For every legal parameterization Tā‚, Tā‚‚, ... of ~A, there exists a parameterization Sā‚, Sā‚‚, ... of ~B such that A

Would X be a subtype of Y in the following case, given a mapping of F(A,B) = (number, B).

type X = ~<A,B> = {x : B};
type Y = ~<A,B> = A extends number ? {x: B} : never;

However X<string,number> would not be a subtype of Y<string,number>.

I guess I'm not clear if the _existence_ of a mapping is enough. If we take ~A and ~B to be functions, and we want to show that ~B approximates ~A, or ~A is a subtype of ~B, then showing that there is some function ~C, such that ~A is a subtype of (~B . ~C), is not enough I think (C is the mapper). I has to be the case for _all_ mappings.

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

I don't quite follow this example, should the error here not happen? My reading of these is that id1 should have an input constructed by function P that gives a PromiseLike for all _inputs_. Whereas id2 is talking about a value that must be a subytype of applying Promise to A[]. I'm not sure if it's possible to recover the information necessary for id1, from the type of id2. I'm think I might be misunderstanding your point though.

These types would suddenly be different, where they are currently the same

Again, I'm afraid I might be missing your point but it don't know how they are the same. I can't replace RegularNumber with GenericNumber in a type, I would have to given the latter an argument.

I guess I'm not clear if the existence of a mapping is enough. If we take ~A and ~B to be functions, and we want to show that ~B approximates ~A, or ~A is a subtype of ~B, then showing that there is some function ~C, such that ~A is a subtype of (~B . ~C), is not enough I think (C is the mapper). I has to be the case for all mappings.

Yup, you're right, and so is the counter-example you provided. My mapping idea is pretty terrible, actually. I've found other counter-examples. Lots of them. Doesn't work at all.

I've reread this thread and a lot of your replies. I think you're right in a lot of things and I've been looking at the problem in a wrong way. I'll get to what I mean.

I'm not sure I agree that it would be ambiguous if you always assume the highest kind of the constraint until you need it to be lower. This isn't an assertion and if there are other examples that show otherwise then fair enough.

It's either ambiguous or else something becomes impossible to reference. Like in the example above the type constructor of Foo becomes impossible to reference because it's hidden by the type itself. If you write ~Foo or for that matter Foo<*> or ~<A>Foo<A> or anything else that doesn't conflict with other stuff, you wouldn't have this sort of problem.

Yes, you can go around that by defining an alias, though it's not very pretty:

type Foo2<T> = Foo<T>

Like I said, I don't think this is the most important concern.

I don't quite follow this example, should the error here not happen? My reading of these is that id1 should have an input constructed by function P that gives a PromiseLike for all inputs. Whereas id2 is talking about a value that must be a subytype of applying Promise to A[]. I'm not sure if it's possible to recover the information necessary for id1, from the type of id2. I'm think I might be misunderstanding your point though.

That's the correct reading, yeah. But if P extends Promise<A[]> it should be assignable to any place that accepts a Promise<A[]>, such as id1. This is the way it is right now, and what subtyping means.

I don't really think it can be avoided anymore.

Again, I'm afraid I might be missing your point but it don't know how they are the same. I can't replace RegularNumber with GenericNumber in a type, I would have to given the latter an argument.

What I meant is this: the type GenericNumber<T>, for all T, and the type RegularNumber are identical and interchangeable. There is no context in which one would type check and the other would not. Right now, at least.

What we've been talking about would make them different. Because GenericNumber<T> is from a TC, it would be assiganble in places where RegularNumber couldn't be. So it would no longer be interchangeable.

I've thought about this, and I guess it may be unavoidable, and not necessarily bad. Just a new, different behavior.

One way to think about it is that the type parameter becomes part of the "structure" of the type.

I do think TCs will lead to some more different behavior.

New direction

First of all, I think you're right in that the correct subtype relation is the one that doesn't have the mapping:

My first idea was the following. For ~A to be a subtype of ~B:

  1. (a) They must have the same kind (in terms of arity, not constraints).
  2. (b) For every legal parameterization Tā‚, Tā‚‚, ... of ~A, A<Tā‚, Tā‚‚, ...> must be a subtype of B<Tā‚, Tā‚‚, ...>.

The mapping thing... honestly, it's pretty stupid. I don't think there is any way to unify MyPromise<T> extends Promise<T[]> and ~Promise anymore. I'd love to know if someone thinks otherwise.

I'd also love to know if there is an example I'm missing where even this rule doesn't work.

If we do agree that type constructor constraints should be expressed using a subtype relation, which does seem to work very well, we can move to other things.

About the syntax

We're not really in agreement on this subject, it seems. I strongly prefer a prefix syntax similar to ~Promise. Conceptually, the ~ can be seen as a "reference to the TC of" operator or something.

I think I've given several reasons why it's better than alternatives:

  1. Totally unambiguous.

    1. Errors involving this syntax are also unambiguous. If someone forgets to parameterize a type, they won't get errors about type constructors when they don't know what they are.

    2. As a side-effect, existing error texts and logic will not have to be changed. If someone writes Promise the error message will be exactly the same as it is now. It won't have to change to talk about TCs.

  2. Extends well to a structural syntax.
  3. Easy to parse, I believe. Any ~\w appearing where a type is expected will be assumed to indicate a reference to a TC.
  4. Easy to type.

I hope other people can give their opinions.

About unions, intersections, and default type parameters

Are overloaded/mixed kinds of the forms * & (* => *), * | (* => *), and so on, legal? Do they have interesting uses?

I think they're a bad idea and hard to reason about. I'm also not sure what kind of type annotation you would need to disambiguate * | (* => *) so you can construct a type from it.

One way that such kinds can be said to exist right now, is types with default type parameters:

type Example<A = number> = {}

This type can be said to have kind * & (* => *) because it can accept a type parameter to construct a type, but it doesn't have to.

I believe that default type parameters should be a form of shorthand, not a way of describing types. So I think the default type parameters should just be ignored when determining the kind of a type.

However, it may make sense to talk about types such as ~Promise | ~Array. They have the same kind, so they're not incompatible. I think this should be supported.

Things that will have to be dealt with

Related situations will have to be dealt with, such as this situation:

type Example = (<~P extends ~Promise>() => P<number>) | (<~M extends ~Map>() => Map<string, number>);

But this doesn't really involve the kind (* => *) | (*, *) => *, but something different

Constructing other TCs?

Like I mentioned before, I don't think it's a good idea to have TCs that construct other TCs, like * => (* => *). They're the norm in languages that support currying and the like, but not in TypeScript.

There is no way that I can see to define such types at all using the ~ syntax and the subtype relation, so it wouldn't require any special rules for forbid them. It would require special rules to make them work.

I guess you could arguably define them structurally like this:

~<A>~<B>{a : A, b : B}

That's pretty much the only way I think.

Interaction with higher-rank functions?

There is a natural, but complex interaction with function types that take type parameters:

type Example<T> = <~P extends ~Promise>(p : P<T>) : P<T>;

Should this interaction be stopped somehow? I can see types like these becoming very complicated.

In general, are there places TC parameters should not appear?

Is my structural syntax a good idea?

I don't think it should be implemeneted right away, but I still think my structural syntax is a good idea. It lets you:

  1. Use local identifiers like other type parameters in your constraints.
  2. Partially apply type constructors, in a very explicit and flexible way: ~<A>Map<string, A>, ~<A, B>Map<B, A>, and so on.
  3. Every other aspect of a type has a structural syntax, so TCs should also have such syntax.

That said, TCs can totally work without this, and the first PR probably wouldn't involve them.

Interaction with conditional types

How will the feature work with conditional types? Should you be able to do this?

type Example<~P extends ~PromiseLike> = ~P extends ~Promise ? 0 : 1

I'm not entirely sure myself. Still haven't completely digested conditional types yet.

Overload resolution

I have a feeling this one is going to be hard to do. This is actually pretty important because different forms of overload resolution would end up constructing different types.

That said, I can't come up with any good examples right now.

You know, much of this would've been a moot point if a well-defined intermediary language was used to describe TypeScript as a starting point. For example: System F<: or one of the sound dependent type systems such as Simplified Dependent ML .

I'd be honestly surprised if this get resolved before
https://github.com/Microsoft/TypeScript/issues/14833

I feel #17961 could probably solve this indirectly. See this gist for more details.

Note that the Bifunctor and Profunctor types are a bit complex at the constraint level - it'd be much simpler if I had obvious universal types to work with, rather than the infer T that is purely limited to conditional types. Also, it'd be nice if I could've used this as the "return" type (that is purely type level) - that would've made most of my interfaces easier to define.

(I'm not a heavy TS user, so I may have made mistakes. @tycho01 Could you take a look at that to see if I screwed up anywhere in the type mess? The reason I ask is because you're the one behind the above PR, and I've seen some of your other experiments and utilities.)

@isiahmeadows @tycho01 Wow...

You're right. If I understand it correctly, it has pretty much the same results.

There are a few differences. But functionally, they are pretty much identical, and I think these differences can be resolved.

Not possible to infer the correct type function

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;

Here you can infer ~Promise and ~Bluebird from p. However, if you do it like this:

function example<F extends <T>(t: T) => PromiseLike<T>>(p : F(number)) : F(string)

I highly doubt that this would work:

example(null as Promise<number>)

There is no way to infer that F is meant to be:

<T>(t : T) => Promise<T>

Because this function is not considered special in any way. Whereas with TCs, some types essentially have an "implicit" type-level function: their TC.

Not possible to reference existing TCs easily

You can't do ~Promise like in my proposal. You'd have to encode the type directly, using a structural format:

type PromiseTC = <T>() => Promise<T>

True, and that is a concern. That is more of a type inference issue where you need the ability to infer the generic function itself from a known argument type (the reverse of what usually happens). It's solvable in a way general enough to work for most cases, but it requires a new special case that's non-trivial.

It might be solvable in part via strategic use of NoInfer<T>, but I'm not 100% sure how that would need to be done, and how much it could address even the common case.

@GregRos

I'm not strongly in favour of any syntax it's more just my preference, there are plenty of merits to ~. I think the main thing to consider would be whether syntax for explicit kind annotations are required because inference isn't always possible.


The mapping thing... honestly, it's pretty stupid. I don't think there is any way to unify MyPromise extends Promise

I think the mapping thing could still be a useful notion, but in the case above I don't think we should ever be trying to unify ~MyPromise with ~Promise, the thing that needs to be unified is ~MyPromise and ~<T>Promise<T[]>, which we could also write ~(Promise . []). I think the thing that was missing is that the mapping needs to be part of the subtyping relation: it's as much a part of the constructor as Promise. In that example the mapping is just the list constructor.

interface A<T> {
    x: T;
} 

interface B<T> {
    x: T[];
}

Does ~<T>B<T> extend ~<T>A<T[]>? Yes. Does ~<T>B<T> extend ~<T>A<T>? No. But ultimately they are two unrelated questions.

If we do agree that type constructor constraints should be expressed using a subtype relation, which does seem to work very well, we can move to other things.

Yes I think it seems like a nice way of describing things.


Not possible to infer the correct type function

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;
Here you can infer ~Promise and ~Bluebird from p.

This isn't an assertion, more an open question as I'm not entirely sure how the type checking works. I thought that, using the interface A above as an example, types A<number> and {x: number} are indistinguishable and therefore I'm not sure that it would be possible to infer the constructor from the type returned from an application of the constructor. Would it be possible to recover P from P<number>? I'm sure things could be changed to support this, I'm just wondering what it does now.

Cross-responding from #17961, but I'm not sure how to make @isiahmeadows's approach work, unfortunately. I fear the backward inference on type calls is non-trivial.

So it looks like based on an input Promise<number> or Bluebird<number> we wanna be able to infer unapplied versions of these types such that we could reapply them with e.g. string. That sounds tough though.

Even if input types are like this instead of some structural equivalent (we're a structurally typed language, right?), this reasoning also gets murky if e.g. Bluebird had two param types instead, at which point our <string> type param application might no longer make sense.
I'm not sure of a nice solution there. (disclaimer: I've gotten a bit behind on the discussion here.)

@tycho01 Would all these problems go away if people explicitly instantiated T?

I think that is reasonable, given that I doubt inference can be solved for all cases anyway.

@jack-williams: not with #17961 so far, but I think using it for dispatching might help:

let arr = [1, 2, 3];
let inc = (n: number) => n + 1;
let c = arr.map(inc); // number[]
let map = <Functor extends { map: Function }, Fn extends Function>(x: Functor, f: Fn) => x['map'](f); // any on 2.7 :(
let e = map(arr, inc);

@tycho01 Yes I realised my suggestion was terrible because T isn't instantiated at the method calls.

Would something like the following work?

interface TyCon<A> {
    C: <A>(x: A) => TyCon<A>
}

interface Functor<A> extends TyCon<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(this: this["C"](A), f: (x: A) => B): this["C"](B);
}

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

@jack-williams I guess the question should be how it would compare in behavior to the ADT implementation in fp-ts, but this looks like it could work, yeah. Probably also without the TyCon.

@jack-williams @isiahmeadows:

I tried the idea at try flow, since it already has $Call available. For me it seems to become unresponsive somehow though...

interface Functor<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(f: (x: A) => B): $Call<$ElementType<this, "C">, B>;
}
// this: $Call<$ElementType<this, "C">, A>, 
// ^ flow doesn't seem to do `this` params

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

let o: Option<string>;
let f: (s: string) => number;
let b = o.fmap(f);

@tycho01 I think you can't just get properties with $ElementType from this in flow

@tycho01 you actually can't get this to work in typescript too
playground: https://goo.gl/tMBKyJ

@goodmind: hm, looks like it infers Maybe<number> instead of Functor<number> after copying over fmap from Functor to Maybe.
With type calls I guess that would improve to having just the type there rather than needing the run-time implementation for the type.
Now, functors would need their own fmap implementations already. That would suck for the derived methods though.
Back to square one. :/

I'm planning to release an alpha version ASAP, but you can follow along with me writing the examples in that issue to already get a feel for it.

This particular issue is a bit long to go through entirely, but what I'm looking for is simple contained, but real examples of code that you aren't able to type because of lack of parameterized generic types. I think I can type most of them (provided they don't rely on abstract lifted type constructors). Feel free to open issues in the above repo with code and I'll type them for you if I can (or you can post them here too).

Heads up I've started an attempt at implementing this at #23809. It's still very incomplete but check it out if you are interested.

I promised you guys a simple example, here it is. This uses a few tricks I learned from writing my library.

type unknown = {} | null | undefined;

// Functor
interface StaticFunctor<G> {
    map<F extends Generic<G>, U>(
        transform: (a: Parameters<F>[0]) => U,
        mappable: F
    ): Generic<F, [U]>;
}

// Examples
const arrayFunctor: StaticFunctor<any[]> = {
    map: <A, B>(fn: (a: A) => B, fa: A[]): B[] => {
        return fa.map(fn);
    }
};
const objectFunctor: StaticFunctor<object> = {
    map: <A, B>(fn: (a: A) => B, fa: A): B => {
        return fn(fa);
    }
};
const nullableFunctor: StaticFunctor<object | null | undefined> = {
    map: <A, B>(
        fn: (a: A) => B,
        fa: A | null | undefined
    ): B | null | undefined => {
        return fa != undefined ? fn(fa) : fa;
    }
};

const doubler = (x: number) => x * 2;

const xs = arrayFunctor.map(doubler, [4, 2]); // xs: number[]
const x = objectFunctor.map(doubler, 42); // x: number
const xNull = nullableFunctor.map(doubler, null); // xNull: null
const xSome = nullableFunctor.map(doubler, 4 as number | undefined); // xSome: number | undefined

const functor: StaticFunctor<unknown | any[]> = {
    map(fn, fa) {
        return Array.isArray(fa)
            ? arrayFunctor.map(fn, fa)
            : fa != undefined
                ? objectFunctor.map(fn, fa)
                : nullableFunctor.map(fn, fa);
    }
};

const ys = functor.map(doubler, [4, 2]); // ys: number[]
const y = functor.map(doubler, 42); // y: number
const yNull = functor.map(doubler, null); // yNull: null
const ySome = functor.map(doubler, 42 as number | undefined); // ySome: number | undefined

// Plumbing
interface TypeProps<T = {}, Params extends ArrayLike<any> = never> {
    array: {
        infer: T extends Array<infer A> ? [A] : never;
        construct: Params[0][];
    };
    null: {
        infer: null extends T ? [never] : never;
        construct: null;
    };
    undefined: {
        infer: undefined extends T ? [never] : never;
        construct: undefined;
    };
    unfound: {
        infer: [NonNullable<T>];
        construct: Params[0];
    };
}

type Match<T> = T extends infer U
    ? ({} extends U ? any
        : TypeProps<U>[Exclude<keyof TypeProps, "unfound">]["infer"]) extends never
    ? "unfound"
    : {
        [Key in Exclude<keyof TypeProps, "unfound">]:
        TypeProps<T>[Key]["infer"] extends never
        ? never : Key
    }[Exclude<keyof TypeProps, "unfound">] : never;


type Parameters<T> = TypeProps<T>[Match<T>]["infer"];

type Generic<
    T,
    Params extends ArrayLike<any> = ArrayLike<any>,
    > = TypeProps<T, Params>[Match<T>]["construct"];

I updated and simplified the sample, here's a playground link too:
Playground

I added an NPM library for the above, so you can work with it easier. Currently in alpha until I get proper testing in, but should help you guys trying to write HKTs.

Github Link

I've been playing with a simple approach to simulating HKTs by using conditional types to substitute virtual type variables within a saturated type:

declare const index: unique symbol;

// A type for representing type variables
type _<N extends number = 0> = { [index]: N };

// Type application (substitutes type variables with types)
type $<T, S, N extends number = 0> =
  T extends _<N> ? S :
  T extends undefined | null | boolean | string | number ? T :
  T extends Array<infer A> ? $Array<A, S, N> :
  T extends (x: infer I) => infer O ? (x: $<I, S, N>) => $<O, S, N> :
  T extends object ? { [K in keyof T]: $<T[K], S, N> } :
  T;

interface $Array<T, S, N extends number> extends Array<$<T, S, N>> {}

// Let's declare some familiar type classes...

interface Functor<F> {
  map: <A, B>(fa: $<F, A>, f: (a: A) => B) => $<F, B>;
}

interface Monad<M> {
  pure: <A>(a: A) => $<M, A>;
  bind: <A, B>(ma: $<M, A>, f: (a: A) => $<M, B>) => $<M, B>;
}

interface MonadLib<M> extends Monad<M>, Functor<M> {
  join: <A>(mma: $<M, $<M, A>>) => $<M, A>;
  // sequence, etc...
}

const Monad = <M>({ pure, bind }: Monad<M>): MonadLib<M> => ({
  pure,
  bind,
  map: (ma, f) => bind(ma, a => pure(f(a))),
  join: mma => bind(mma, ma => ma),
});

// ... and an instance

type Maybe<A> = { tag: 'none' } | { tag: 'some'; value: A };
const none: Maybe<never> = { tag: 'none' };
const some = <A>(value: A): Maybe<A> => ({ tag: 'some', value });

const { map, join } = Monad<Maybe<_>>({
  pure: some,
  bind: (ma, f) => ma.tag === 'some' ? f(ma.value) : ma,
});

// Not sure why the `<number>` annotation is required here...
const result = map(join<number>(some(some(42))), n => n + 1);
expect(result).toEqual(some(43));

Project here: https://github.com/pelotom/hkts

Feedback welcome!

@pelotom I like the lightness of the syntax of your approach. There are two other approaches that haven't been mentioned in this thread yet that might stir some creativity in how current and future solutions are produced. Both are Object-Oriented solutions to this problem.

  1. Bertrand Meyer described a way to simulate generic types in his 1988 book "Object-oriented Software Construction" .

The examples are in Eiffel, but a rough translation to TypeScript looks like this:

https://gist.github.com/mlhaufe/089004abd14ad8e7171e2a122198637f

You'll notice they can get pretty heavy due to the need for intermediate class representations, but with a form of class-factory or with the TypeScript Mixin approach this could be reduced significantly.

There may be some applicability to #17588

  1. The second approach is used when simulating Object Algebras (and Abstract Factories):

C<T> is represented by App<t,T> where T is the class, and t is a unique tag associated with C

interface App<C,T> {}

Sample:

interface IApp<C,T> {}

interface IList<C> {
    Nil<T>(): IApp<C,T>
    Cons<T>(head: T, tail: IList<C>): IApp<C,T>
}

// defining data
abstract class List<T> implements IApp<typeof List, T> {
    // type-safe down-cast
    static prj<U>(app: IApp<typeof List, U>): List<U> { return app as List<U> }
}
class Nil<T> extends List<T> { }
class Cons<T> extends List<T> {
    constructor(readonly head: T, readonly tail: List<T>) {
        super()
    }
}

// The abstract factory where the HKT is needed
class ListFactory<T> implements IList<typeof List> {
    Nil<T>(): IApp<typeof List, T> { return new Nil() }
    Cons<T>(head: T, tail: IApp<typeof List, T>): IApp<typeof List, T> {
        return new Cons(head, tail)
    }
}

You can see more details and justification in the following paper under section 3.5 "Emulating Type-Constructor Polymorphism":

https://blog.acolyer.org/2015/08/13/streams-a-la-carte-extensible-pipelines-with-object-algebras/

@metaweta, can you rename this issue to Higher kinded types in TypeScript so it gets a better visibility from google search please?

Perhaps our wise and benevolent repository maintainers (e.g., @RyanCavanaugh, @DanielRosenwasser ) could edit the title, if such a change be deemed worthy of their intervention?

Iā€™m curious to know what it means that this has been moved from community to backlog. Is this something the core team is more seriously considering now or does is simply mean the team has decided this isnā€™t a good community candidate?

Found it: "Community" milestone is apparently deprecated in favor of the "Backlog" one, and so this issue was probably migrated in kind.

Not a TS member, just someone who decided to click the link where it was re-milestoned.

+1

Here's something I was just trying to build that seems like a really practical case for higher-kinded types.

I want to create an abstraction for a database that can run either synchronously or asynchronously. Rather than use callbacks and hack around that, I wanted to use a generic. Here's what I'd like to do:

type Identity<T> = T

interface DatabaseStorage<Wrap<T> extends Promise<T> | Identity<T>> {
    get(key: string): Wrap<any>
    set(key: string, value: any): Wrap<void>
}

This would be really powerful!

@ccorcos thats called MTL-style. You can take a look at https://github.com/gcanti/fp-ts/blob/master/tutorials/mtl.ts for a pure functional example with fp-ts.

@mlegenhausen I'm sorry, but I have a hard time following that example.

Whenever I dig into fp-ts, I worry that things are getting so complicated that they're going to be brittle. @pelotom's example looks easier to follow though...

Any reason this doesn't get adopted into TypeScript?

@ccorcos IMHO even when I recommended the example from fp-ts I would not recommend MTL/tagless style at all. You add an extra layer of abstraction to every effectful monad that you need to manage manually cause typescript is not capable of detecting which monad you want to use and this is where things gets complicated. What I see from the fp-ts community is to use one async monad (I would recommend TaskEither) and stick with it. Even in testing the benefits of MTL are not worth the hassle you get in your non-testing code. hyper-ts based on fp-ts is one example of a library that dropped support for MTL recently.

Interesting... hyper-ts looks really cool...

I came up with a lightweight higher-kinded type encoding based on F-bounded polymorphism: https://github.com/strax/tshkt

The benefit of this approach is that you do not need a lookup table (a single conditional type or an object with string keys) to associate type constructors to types. The technique can also be used to encode application of type-level generic functions (think ReturnType<<T>(value: T) => Array<T>>).

It's still a proof of concept so feedback on the viability of this approach is greatly appreciated!

I'll have a look at that @strax, that looks really cool!

Meanwhile, here's a silly example of what we can do now:

type Test1 = Ī»<Not, [True]>;        // False
type Test2 = Ī»<And, [True, False]>; // False
type Test3 = Ī»<And, [True, True]>;  // True

// Boolean

interface True extends Func {
    expression: Var<this, 0>;
}

interface False extends Func {
    expression: Var<this, 1>;
}

interface Not extends Func {
    expression: Ī»<Var<this, 0>, [False, True]>
}

interface And extends Func {
    expression: Ī»<Var<this, 0>, [Var<this, 1>, Var<this, 0>]>
}

// Plumbing

type Func = {
    variables: Func[];
    expression: unknown;
}

type Var<F extends Func, X extends number> = F["variables"][X];

type Ī»<Exp extends Func, Vars extends unknown[]> = (Exp & {
    variables: Vars;
})["expression"];

I would like to add De Bruijn indices, since that would mean we don't need the interfaces anymore, but it'd require some tuple math I think and I'm trying to avoid that.

Proposal

Higher Order, Lamda, Reference types

Pass a Type as a Reference

Simply put, a reference type or a higher order type would allow one to defer the parameters taken by a type for later, or even infer type parameters (generics) afterwards. But why should we care?

If we can pass a type as a reference, it means that we can delay TypeScript from evaluating a type until we decide to do so. Let's take a look at a real world example:

Preview with Pipe

Imagine that you are developing a generic type for pipe. Most of the work is about checking that the functions to be piped are indeed pipe-able, otherwise we would raise an error to the user. To do so, we'd use a mapped type to pipe the types of the functions like pipe(...) does:

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
    ?  Fns[K]
    // For all the other functions, we link input<-output
    : (arg:  Return<Fns[Pos<Prev<IterationOf<K & string>>>]>) =>
        Return<Fns[Pos<IterationOf<K & string>>]>;

Now, we just have to repeat this over the functions with a mapped type:

type  Piper<Fns  extends  Function[]> = {
    [K  in  keyof  Fns]:  PipeSync<Fns, K>
}

(see the full implementation)

Now we are able to pipe functions together and TypeScript can give us warnings:

declare  function  pipe<Fns  extends  F.Function[]>(...args:  F.Piper<Fns>):  F.Pipe<Fns>

const  piped  =  pipe(
    (name:  string, age:  number) => ({name, age}),
    (info: {name:  string, age:  number}) =>  `Welcome, ${info.name}`,
    (message:  object) =>  false, // /!\ ERROR
)

It works! we got a proper error:

Argument of type '(message: object) => boolean' is not assignable to parameter of type '(arg: string) => boolean'.

The Problem

But there is a problem. While this works very well for simple operations, you'll find that it fails completely when you start using generics (templates) on the functions you pass to it:

const  piped  =  pipe(
    (a:  string) =>  a,
    <B>(b:  B) =>  b, // any
    <C>(c:  C) =>  c, // any
)

type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B,
    <C>(c:  C) =>  C,
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  unknown,
//     (c:  unknown) => unknown
// ]

In both cases, TypeScript lost track of the function types.
> This is where higher order types come into play <

Syntax

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
+   ?  *(Fns[K]) // this will preserve the generics
    // For all the other functions, we link input<-output
+   :  *( // <- Any type can be made a reference
+       <T>(arg:  T) => Return<*(Fns[Pos<IterationOf<K  &  string>>])>
+       // vvv It is now a reference, we can assign generics
+       )<Return<*(Fns[Pos<Prev<IterationOf<K  &  string>>>])>>
+       // ^^^ We also tell TS not to evaluate the previous return
+       // and this could be achieved by making it a reference too

In short, we manually and dynamically inferred the generics with *. In fact, using * deferred the evaluation of the generics. So the * has different behaviors, depending on the context. If the * is on a type that:

  • can receive generics: It takes over its generics / gets its reference
  • is a generic itself: It defers evaluation until the reference is known. For this, a reference tree is built from the parent(s) down. In other words, refrences can be nested. This is exactly what happens above with Return<*(Fns[Pos<Prev<IterationOf<K & string>>>])> that got assigned to T. In this context, we can say that * "protects" against immediate evalutation.
  • is none of the above: It does nothing, resolves to that same type
type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B
    <C>(c:  C) =>  C
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  string,
//     (c:  string) =>  string
// ]

So TypeScript should start/continue evaluating only if the generic has been provided, and block the evaluation when needed (incomplete generic). At the moment, TS evaluates in one shot by turning generics into unknown types. With this proposal, when something cannot be resolved:

type  piped  =  Piper<[
    <A>(a:  A) =>  A, // ?
    <B>(b:  B) =>  B, // ?
    <C>(c:  C) =>  C, // ?
]>
// [
//     <A>(a:  A) =>  A,
//     (b:  A) =>  A,
//     (c:  A) =>  A
// ]

Details

The * retrieves a reference to a type, enabling manipulations on its generics. So placing the wildcard in front of a type retrieves a reference to it:

*[type]

Retrieving a reference to a type automatically enables generics manipulation:

*[type]<T0, T1, T2...>

The generics are only consumed/set by the targeted type if it can be. So doing this:

*string<object, null> // Will resolve to `string`

But it could also be checked by TypeScript itself, whether it should display a warning or not. But internally, TS should do nothing from this.

I also thought that it was a good idea to use * as it can symbolize a pointer to something (like in C/C++ languages), and isn't borrowed by TypeScript.

Higher Order Types

Now that we've seen how it works in its most basic form, I would like to introduce the core concept: lambda types. It would be nice to be able to have anonymous types, similar to callbacks, lambdas, references in JavaScript.

The example above shows how to take over the generics of a function. But since we are talking about references, any type can be used in conjunction with *. Simply put, a type reference is a type that we can pass around but that hasn't received it's generics yet:

type  A<T  extends  string> = {0:  T}
type  B<T  extends  string> = [T]
type  C<T  extends  number> = 42

// Here's our lamda
type  Referer<*Ref<T  extends  string>, T  extends  string> =  Ref<T>
// Notice that `T` & `T` are not in conflict
// Because they're bound to their own scopes

type  testA  =  Referer<A, 'hi'> // {0: 'hi'}
type  testB  =  Referer<B, 'hi'> // ['hi']
type  testC  =  Referer<C, 'hi'> // ERROR

Higher Kinded Types

interface Monad<*T<X extends any>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

Search Terms

higher #order #type #references #lambda #HKTs

@pirix-gh if you read up just a few messages, you can see that a lot of what you ask for is either already possible or has already been asked for.

I did read them, I thought I could summarize my ideas like everyone else did (for a all-in-one solution), mostly about the syntax.

I edited the above proposal for a better explanation of how we could chain references, and fixed the way a type like Pipe would work with it (there were a few mistakes regarding the logic of it).

Any update?

Still no update? In my opinion, this issue ranks as the number one obstacle to TypeScript achieving its full potential. There are so many instances where I try to type my libraries properly, only to give up after a long struggle, realizing that I have run up against this limitation again. It is pervasive, showing up even in seemingly very simple scenarios. Really hope it will be addressed soon.

interface Monad<T<X>> {
    map1<A, B>(f: (a: A) => B): (something: A) => B;

    map<A, B>(f: (a: A) => B): (something: T<A>) => T<B>;

    lift<A>(a: A): T<A>;
    join<A>(tta: T<T<A>>): T<A>;
}

type sn = (tmp: string) => number

function MONAD(m: Monad<Set>,f:sn) {
    var w = m.map1(f);    // (method) Monad<Set>.map1<string, number>(f: (a: string) => number): (something: string) => number
    var w2 = m.map(f);    // (method) Monad<Set>.map<string, number>(f: (a: string) => number): (something: Set<string>) => Set<number>
    var q = m.lift(1);    // (method) Monad<Set>.lift<number>(a: number): Set<number>
    var a = new Set<Set<number>>();
    var w = m.join(q);    // (method) Monad<Set>.join<unknown>(tta: Set<Set<unknown>>): Set<unknown>.  You could see that typeParameter infer does not work for now.
    var w1 = m.join<number>(q);    // (method) Monad<Set>.join<number>(tta: Set<Set<number>>): Set<number>
}

A lot of work still need to be done, like: fix quickinfo, typeParameter infer, add error message, hightlight same typeConstructor.....
But it starts to work, and here is what I could get for now.
The example interface is from @millsp https://github.com/microsoft/TypeScript/issues/1213#issuecomment-523245130 , the conclusion is really really helpful, great thanks to that.

I hope communicity could provide more user cases like that, to check whether current way works for most situations.

It would also be sweet to provide some info about HKT/function programming/lambda(when I say lambda, I mean the math, I could only find example written by some language, without math)

Here are things that help me a lot:

@ShuiRuTian Regarding that m.join(q) returning Set<unknown>, I assume --noImplicitAny causes that to emit a warning as well?

I hope community could provide more user cases like that, to check whether current way works for most situations.

It would also be sweet to provide some info about HKT/function programming/lambda(when I say lambda, I mean the math, I could only find example written by some language, without math)

Without going any further, I recently tried to create a generic curried filter function, and I wanted it to do something like this:

const filterNumbers = filter(
    (item: number | string): item is number => typeof item === "number"
);

const array = ["foo", 1, 2, "bar"]; // (number | string)[]
const customObject = new CustomObject(); // CustomObject<number | string>

filterNumbers(array); // number[] inferred
filterNumbers(customObjectWithFilterFunction); // CustomObject<number> inferred

And I don't have a way of actually doing that, because I need a way of having telling TypeScript "return the same type you received, but with this other parameter". Something like this:

const filter = <Item, FilteredItem>(predicate: (item: Item) => item is FilteredItem) =>
  <Filterable<~>>(source: Filterable<Item>): Filterable<FilteredItem> => source.filter(predicate);

@lukeshiru yeah, essentially this is https://pursuit.purescript.org/packages/purescript-filterable/2.0.1/docs/Data.Filterable#v:filter
There're lots of other similar usecases for HKT in TypeScript.

@isiahmeadows I tried it. You are right.
@lukeshiru and @raveclassic Thanks! This feature seems pretty resonable. I would have a look at this after reading https://gcanti.github.io/fp-ts/learning-resources/

I am stuck and I don't know what is the current āø˜workaroundā€½...
I am trying to implement the Chain specification:

m['fantasy-land/chain'](f)

A value that implements the Chain specification must also implement the Apply specification.

a['fantasy-land/ap'](b)

I did a FunctorSimplex which is then extended by a FunctorComplex then extended by the Apply but now that I want to extend Apply as a Chain it is breaking...

So I need that (image below and link to code):

(I need to pass a type to the ApType so that on line 12 the Apply is not Ā« hardcoded Ā» but is generic... to also take any types extended from an IApply)

Screenshot

Permalink to the code snippet lines 11 to 22 in 7ff8b9c

```typescript
export type ApType = (
ap: Apply<(val: A) => B>,
) => IApply;

/* [...] */

export interface IApply extends FunctorComplex {
/** Fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b */
ap: ApType
;
}
```

Ā Referenced into a Stack Overflow question: TypeScript Generic Types problem: Workaround required

@Luxcium Until TS has support for higher-kinded-types, only emulation of them is possible. You might want to take a look there to see how it is possible to achieve:

Until TS has support for higher-kinded-types, only emulation of them is possible. You might want to take a look there to see how it is possible to achieve

Tanks a lot @kapke I am probably too much into FP theses days and since in Javascript one can return a function from a function we can write pseudoFnAdd(15)(27) // 42 I would like to be capable, with TypeScript, to write pseudoType<someClassOrConstructor><number> // unknown but I am a script kiddie, not a _Ā°some kind of person that studied for a long time at the university or somethingĀ°_

This information and lectures (readings) are much appreciated...

Note: I am french speaking, in french the word _lecture(s)_ have the meaning of _readings_ and not 'an angry or serious talk given to someone in order to criticize their behaviour' ...

Probably the following I came up with as a simple workaround without PRs will not work for all cases, but I think it is worth mentioning:

type AGenericType<T> = T[];

type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
  [k in keyof T]: T[k] extends X ? Y : T[k];
};

interface Monad<T> {
  map<A, B>(f: (a: A) => B): (v: Replace<T, Placeholder, A>) => Replace<T, Placeholder, B>;
  lift<A>(a: A): Replace<T, Placeholder, A>;
  join<A>(tta: Replace<T, Placeholder, Replace<T, Placeholder, A>>): Replace<T, Placeholder, A>;
}

function MONAD(m: Monad<AGenericType<Placeholder>>, f: (s: string) => number) {
  var a = m.map(f); // (v: string[]) => number[]
  var b = m.lift(1); // number[]
  var c = m.join([[2], [3]]); // number[]
}
Was this page helpful?
0 / 5 - 0 ratings