Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type aliases requiring type parameters #1616

Closed
danielearwicker opened this issue Jan 8, 2015 · 34 comments

Comments

Projects
None yet
@danielearwicker
Copy link

commented Jan 8, 2015

Sometimes we need to capture a union of types that may be a mixture of primitives, functions, objects, etc. but we want to leave it parameterised (just as we already can with interfaces).

Example

A simple non-recursive example:

type Source<T> = T | (() => T);

Here, a source can either be a plain value or a nullary function that obtains a value.

We can provide uniform access to such sources:

function unwrap<T>(p: Source<T>) {
    return (typeof p === "function") ? p() : p;
}

And then we can specify model interfaces where we we leave open the nature of the source but we tie down the value types:

interface Person {
    name: Source<string>;
    age: Source<number>;
}

e.g. name is a constant, but age depends on when you ask:

var p: Person = {
    name: "John Lennon",
    age: () => ageFromDOB(1940, 10, 9),
}

But we can treat them identically in consuming code:

var n = unwrap(p.name), a = unwrap(p.age);

NB. The above is already possible with union types alone, but the interface Person has to repeat the pattern:

interface Person {
    name: string | (() => string);
    age: number | (() => number);
}

Not so bad for a simple example, but the pattern for a value source might evolve to get more complex and then you have a lot of fiddly updating to do because you "Did Repeat Yourself".

Recursion

The more flexible recursive version:

type Source<T> = T | (() => Source<T>);

A source can either be a plain value or a nullary function that obtains a source (which may be a plain value terminating recursion, or a nullary function that... and so on).

We can again provide uniform access to such sources, either with runtime recursion (risky until tail-call optimisation is widespread):

function unwrap<T>(p: Source<T>) {
    return (typeof p === "function") ? unwrap(p()) : p;
}

Or with a loop:

function unwrap<T>(p: Source<T>) {
    for (;;) {
        if (typeof p !== "function") {
            return p;
        }
        p = p();
    }
}
@zpdDG4gta8XKpMCd

This comment has been minimized.

Copy link

commented Feb 5, 2015

I was surprised that the type alias feature that was shipped with 1.4 doesn't support type parameters. This makes it of a very limited use which was hardly worth the effort of adding them the way they are. Please consider adding the type parameters, so we can say TypeScript supports type aliases.

@bluong

This comment has been minimized.

Copy link

commented Feb 7, 2015

👍 on please allowing for type parameters

@RamIdeas

This comment has been minimized.

Copy link

commented Feb 10, 2015

I would definitely like this. Was there any reason they weren't permitted in the first place?

@mudHOAX

This comment has been minimized.

Copy link

commented Feb 12, 2015

👍 definitely a must have.

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Mar 9, 2015

Any point on a roadmap? When this (definitely a must have) feature will be implemented?

@danquirk

This comment has been minimized.

Copy link
Member

commented Mar 9, 2015

Lately we've been quite busy with ES6 alignment and the recently announced Angular 2.0 related features. We will get to (re)evaluating some of these type system specific issues but there's no concrete date for issues like this at the moment. If there are other motivating examples/use cases that you have in mind that you think influence the priority or design do let us know.

@mweststrate

This comment has been minimized.

Copy link

commented Mar 23, 2015

👍

type BaseReactClass<P,S> = reactWrapper.BaseReactClass<P,S>;

EDIT in this specific case, import BaseReactClass = reactWrapper.BaseReactClass; works as well

@danihodovic

This comment has been minimized.

Copy link

commented Apr 2, 2015

Is this feature underway?

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Apr 2, 2015

Awaiting it eagerly for writing

type P<T> = Promise<T> | T;
type A<T> = P<P<T>[]>;

function resolve<T>(a: A<T>): T[] { ... }

instead of

function resolve<T>(a: Promise<(Promise<T> | T)[]> | (Promise<T> | T)[]): T[] { ... }
@alshain

This comment has been minimized.

Copy link

commented Apr 20, 2015

👍

1 similar comment
@jrsnyder

This comment has been minimized.

Copy link

commented Apr 20, 2015

👍

@RyanCavanaugh RyanCavanaugh added this to the Community milestone Apr 27, 2015

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Apr 27, 2015

Approved. Anders suggests this will be non-trivial to implement, essentially requiring a new "kind" of type.

Any PR should have a large set of tests, especially in the area of self-referential type aliases and compatibility there.

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2015

@RyanCavanaugh can new "kind" of types be avoided if type-aliases will be implemented as higher-level types?

// List :: (* -> *)
type List = Array                               

// Of :: ((* -> *), *) -> *
// or nice but unrealistic:
// Of :: (* -> *) -> * -> *    
type Of<Container,Element> = Container<Element> 

// Numbers :: *
type Numbers = Of<List,Number>                  

Would it be backward compatible to what we have now?

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Apr 28, 2015

I don't think so. The issue is that the any use of a type alias is always equivalent to a thing that we already have implemented (object type, primitive type, union type etc.).

A counterexample to that is generic instantiation -- it's never the case today that Foo<string> is anything but an object type, but with generic type parameters in type aliases, it could be a union type. So there's the notion of a new "kind" of thing that is generic, but doesn't always produce an object type.

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2015

@RyanCavanaugh Oh, I see, what you mean. Of course a new kind of types emerges here.
I'd say that HighOrderType would be an ideal new kind for types that will cover all needs for generic parameters for type aliases, and introduce new powerful and consistent feature.
Here HigherOrderType is a classical Λ-term

τ ::= φ | τ<τ{,τ}> | Λα{,α}.τ   

or closer to the syntax:

τ ::= φ | τ<τ{,τ}> | <α{,α}>τ   

where

  • φ – is one of the types that you have already implemented (let's call it final type);
  • τ<τ{,τ}> — type application (generalisation of generic class instantiation);
  • Λα{,α}.τ or <α{,α}>τ — type abstraction (generalisation of generic class definition) where α is a formal type.

Here we treat all names of generic classes as values from the universe of higher-order types. Union can be established only between types that are β-reducible to final types. The same holds for every usage of a type when the type used to constrain a variable, field, parameter or return value. In these cases only types that are β-reducible to final types should be used. Otherwise Compile-time error should be thrown.

It would be really cool and consistent to have full featured higher-order types in TS

type P<T> = T | Promise<T>

just a syntax sugar to

type P = <T>(T | Promise<T>)

It's very interesting what @ahejlsberg would say about it. Are there practical/pragmatical obstacles to implementing it?

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2015

UPD. More text added to the previous comment -)

@danielearwicker

This comment has been minimized.

Copy link
Author

commented Apr 29, 2015

@Artazor I'm inclined to think this would be a separate feature suggestion. It's not covered by my examples. Specifically, what's not is highlighted by your example:

type Of<Container,Element> = Container<Element> 

i.e. treating the type parameter Container as a generic type rather than a complete type. That's not currently possible anywhere in TS.

Having taken my first look at the tsc code today I can definitely agree with the summary from @RyanCavanaugh and Anders! The code in checkTypeRelatedTo (checker.ts) is organised around the assumption that object types are the only context in which type parameters must be mapped to type arguments.

In my limited understanding I got as far as a function resolveTypeReferenceMembers that sets up a TypeMapper from typeParameters to typeArguments but by that point we're deep into the assumption of structural checking of members of an object. So I guess there would need to be some new if-branches in checkTypeRelatedTo to detect and deal with AliasType, set up the TypeMapper and do the same kind of deep check that is currently done for object types.

So its not a quick change to support my examples above, let alone higher-ordered types in which a type parameter can itself be treated as a generic type.

@Artazor

This comment has been minimized.

Copy link
Contributor

commented May 4, 2015

@danielearwicker surely it can be a separate feature, but in that case I'm afraid that it will not be implemented at all. And it will disappoint me.

I hope you'd catch the following fictional analogy:


Imagine that in early implementation of some language in an alternative reality (where people not yet discovered OOP) you realised, that you need to implement a very special feature: when you extend a class you should be able to change the implementation of the existing method (the method overriding in our world). Meanwhile applications you've considered required only one particular method to be overridable, so you start thinking that every class should have one such method.

You choose a very special magical name to it, and design your language and compiler according to this idea. And, of course, a whole bunch of applications will benefit from this single polymorphic method. Then hacks with switch inside that single overridable method will help to achieve effect of overriding multiple methods, and so on.

Some people can start to ask if it will be worth to allow overriding multiple methods by annotating them as "overridable" ("virtual" in our world), but your compiler is already built on the top of idea, that only one method can be overridden, you implemented special semantics, tricks, optimisations and so on. So if you want to allow multiple methods to be overridable then you have to rollback all these efforts and redesign overriding feature, to make it more general (with high risk to break a backward compatibility). Or you may try to implement this as a separate feature, that duplicates already existing feature but is more general. In that case you will face with a whole matrix of edge cases of interoperability between new feature and old ones, that will probably lead to the nightmare. So the most pragmatic scenario, in that case, is to stitch with single overridable method forever (if you want multiple overridable methods - choose/fork/implement another language).


This sad story is ready to happen with HighOrderTypes in TypeScript. However, it may be already too late to introduce them (by the implementation).

My implicit argument for the HighOrderType is that if you have

class MyVeryLongClassName<T> { ... }

then you can already write

type A = MyVeryLongClassName;

that is a legitimate short form of the future

type A<T> = MyVeryLongClassName<T>;

and both form should be supported (this will be hold automatically if high order types will be implemented)

Also, #2559 will be automatically resolved.

Still want to hear from @ahejlsberg and @RyanCavanaugh. Pleeeease, say that it is not too late to implement it! (?)

@balmychan

This comment has been minimized.

Copy link

commented May 14, 2015

👍

1 similar comment
@knazeri

This comment has been minimized.

Copy link

commented May 16, 2015

👍

@bluong

This comment has been minimized.

Copy link

commented May 26, 2015

Still awaiting on this 👍

@markvandenbrink

This comment has been minimized.

Copy link

commented Jun 4, 2015

+1

@mariusschulz

This comment has been minimized.

Copy link
Contributor

commented Jun 4, 2015

👍

It would be great to be able to define type aliases like this:

type Predicate<T> = (element: T) => boolean;
@robertpenner

This comment has been minimized.

Copy link

commented Jun 4, 2015

👍

1 similar comment
@blobor

This comment has been minimized.

Copy link

commented Jun 4, 2015

👍

@flaticols

This comment has been minimized.

Copy link

commented Jun 4, 2015

👍
It would be great and helpful feature

@ahejlsberg

This comment has been minimized.

Copy link
Member

commented Jun 5, 2015

Since y'all asked: #3397. Enjoy!

@ahejlsberg ahejlsberg modified the milestones: TypeScript 1.6, Community Jun 5, 2015

@ahejlsberg ahejlsberg self-assigned this Jun 5, 2015

@mhegazy mhegazy added Committed and removed help wanted labels Jun 5, 2015

@danielearwicker

This comment has been minimized.

Copy link
Author

commented Jun 5, 2015

👏 What a legend.

@zpdDG4gta8XKpMCd

This comment has been minimized.

Copy link

commented Jun 5, 2015

Awesome!
On Jun 5, 2015 5:59 PM, "Anders Hejlsberg" notifications@github.com wrote:

Since y'all asked: #3397
#3397. Enjoy!


Reply to this email directly or view it on GitHub
#1616 (comment)
.

@Artazor

This comment has been minimized.

Copy link
Contributor

commented Jun 5, 2015

-)

@tinganho

This comment has been minimized.

Copy link
Contributor

commented Jun 5, 2015

👍

@danihodovic

This comment has been minimized.

Copy link

commented Jun 10, 2015

(Y)

@HerringtonDarkholme

This comment has been minimized.

Copy link
Contributor

commented Jun 11, 2015

Awesome! My first try on this is type classOf<T> = {new(): T}

@CyrusNajmabadi

This comment has been minimized.

Copy link
Contributor

commented Jun 11, 2015

@HerringtonDarkholme That seems like an odd use of generic type aliases. An interface seems like a better choice here:

interface classOf<T> {
    new(): T;
}

@shiwano shiwano referenced this issue Oct 31, 2015

Closed

Support for new features of TypeScript. #4

11 of 11 tasks complete

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.