-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Comments
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. |
👍 on please allowing for type parameters |
I would definitely like this. Was there any reason they weren't permitted in the first place? |
👍 definitely a must have. |
Any point on a roadmap? When this (definitely a must have) feature will be implemented? |
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. |
👍
EDIT in this specific case, |
Is this feature underway? |
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[] { ... } |
👍 |
1 similar comment
👍 |
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. |
@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? |
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 |
@RyanCavanaugh Oh, I see, what you mean. Of course a new kind of types emerges here.
or closer to the syntax:
where
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
just a syntax sugar to
It's very interesting what @ahejlsberg would say about it. Are there practical/pragmatical obstacles to implementing it? |
UPD. More text added to the previous comment -) |
@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:
i.e. treating the type parameter Having taken my first look at the In my limited understanding I got as far as a function 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. |
@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 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! (?) |
👍 |
1 similar comment
👍 |
Still awaiting on this 👍 |
+1 |
👍 It would be great to be able to define type aliases like this: type Predicate<T> = (element: T) => boolean; |
👍 |
1 similar comment
👍 |
👍 |
Since y'all asked: #3397. Enjoy! |
👏 What a legend. |
Awesome!
|
-) |
👍 |
(Y) |
Awesome! My first try on this is |
@HerringtonDarkholme That seems like an odd use of generic type aliases. An interface seems like a better choice here: interface classOf<T> {
new(): T;
} |
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:
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:
And then we can specify model interfaces where we we leave open the nature of the source but we tie down the value types:
e.g.
name
is a constant, butage
depends on when you ask:But we can treat them identically in consuming code:
NB. The above is already possible with union types alone, but the interface
Person
has to repeat the pattern: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:
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):
Or with a loop:
The text was updated successfully, but these errors were encountered: