TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Allow classes to be parametric in other parametric classes

Open metaweta opened this issue 9 years ago • 166 comments

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.

metaweta avatar Nov 19 '14 18:11 metaweta

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.

DanielRosenwasser avatar Nov 19 '14 19:11 DanielRosenwasser

Thanks, corrected.

metaweta avatar Nov 19 '14 19:11 metaweta

I've updated my comment into a proposal.

metaweta avatar Nov 28 '14 22:11 metaweta

:+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

fdecampredon avatar Jan 27 '15 20:01 fdecampredon

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.

RyanCavanaugh avatar Apr 28 '15 00:04 RyanCavanaugh

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>;
}

metaweta avatar Apr 28 '15 17:04 metaweta

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>;
}

DanielRosenwasser avatar Apr 28 '15 22:04 DanielRosenwasser

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.

metaweta avatar Apr 28 '15 23:04 metaweta

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.

metaweta avatar Apr 28 '15 23:04 metaweta

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

metaweta avatar Jun 01 '15 22:06 metaweta

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.

danquirk avatar Jun 01 '15 23:06 danquirk

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

Artazor avatar Dec 10 '15 10:12 Artazor

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

metaweta avatar Dec 10 '15 15:12 metaweta

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

zpdDG4gta8XKpMCd avatar Feb 29 '16 18:02 zpdDG4gta8XKpMCd

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.

RyanCavanaugh avatar Feb 29 '16 18:02 RyanCavanaugh

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> 
}

spion avatar Apr 18 '16 12:04 spion

Would come in handy here too http://stackoverflow.com/questions/36900619/how-do-i-express-this-in-typescript

bcherny avatar Apr 27 '16 22:04 bcherny

:+1:

gneuvill avatar May 25 '16 11:05 gneuvill

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

zpdDG4gta8XKpMCd avatar Jun 06 '16 22:06 zpdDG4gta8XKpMCd

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?

abuseofnotation avatar Jun 07 '16 19:06 abuseofnotation

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

dead-claudia avatar Jul 12 '16 14:07 dead-claudia

@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.

lynn avatar Jul 13 '16 14:07 lynn

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 avatar Jul 13 '16 15:07 Artazor

@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).

dead-claudia avatar Jul 20 '16 06:07 dead-claudia

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 avatar Aug 02 '16 17:08 paldepind

@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.

dead-claudia avatar Aug 12 '16 20:08 dead-claudia

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> {
}

?

wclr avatar Sep 06 '16 08:09 wclr

@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.

dead-claudia avatar Sep 06 '16 14:09 dead-claudia

@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.

wclr avatar Sep 06 '16 14:09 wclr

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 .

dead-claudia avatar Sep 06 '16 15:09 dead-claudia