Uh oh!
There was an error while loading. Please reload this page.
Type aliases - #957
Conversation
Ryan Cavanaugh (RyanCavanaugh)
commented
Oct 24, 2014
Compiling this file with module M{typeW=Window|string;export module N{exportclassWindow{}exportvarp: W;}} |
Yahiko Uzumaki (yahiko00)
commented
Oct 24, 2014
I've translated this specification draft into French here: http://bit.ly/1wvcqyD |
Cyrus Najmabadi (CyrusNajmabadi)
commented
Oct 24, 2014
Are interfaces effectively a type alias to an object type then? i.e. is there any different between: Also, because the right side is a type, i feel that a type annotation would be a better syntax. i.e.:
Finally, are type aliases allowed inside classes/interfaces? For example, can i do: If we don't allow this, then it's not possible to create a type alias to a type that contains a type parameter (which seems unfortunate). ? |
Dan Quirk (danquirk)
commented
Oct 24, 2014
One difference is that as currently implemented the interface Type aliases can't be parameterized so you'd have no way to say what |
Cyrus Najmabadi (CyrusNajmabadi)
commented
Oct 24, 2014
Dan Quirk (@danquirk) Good points. Is there a good reason though to not emit aliases in the .d.ts? Presumably this helps with code clarity on the implementation side, and i would think you would want that in the .d.ts side as well. I definitely get the issue with being unable to instantiate type aliases. However, i still think they would be useful in a class/interface setting, esp. when describing large and unwieldy types (esp those that use type parameters and uninions). |
Dan Quirk (danquirk)
commented
Oct 24, 2014
I tend to agree on the .d.ts front. If you wrote them for clarity in your own code you likely want them in your API, people already use interfaces and classes this way for various things (ex naming certain call signatures). The other difference that I didn't mention before is the original motivation where you can't use an interface to name a set of union types. On the second point, you can use type aliases for instantiated generics, just not open ones. We discussed it for awhile in the design meeting, it wasn't clear there was a great solution for something that does have a workaround (just use |
Anders Hejlsberg (ahejlsberg)
commented
Oct 25, 2014
Cyrus Najmabadi (@CyrusNajmabadi) From a semantic point of view there is no difference between an interface and a type alias for a similar object type (or any other structurally identical type for that matter). You will however see different behavior in error messages and quick info because, just like import aliases, type aliases are always "dereferenced" to the aliased type, whereas interfaces are displayed by their name. One way to think of it is that type aliases are just names for (possibly anonymous) types, whereas interfaces are a kind of type (that always has a name). I don't think using type annotation syntax (i.e. colon) is better. We're not saying that a type alias is something whose type is blah, where saying that it is an alias for blah. I think equals expresses that better and we already use it for import aliases. Once we implement local type declarations in functions and methods (which we need to support ES6 style local classes) you'll be able to reference type parameters. Whether to also allow local type declarations in class and interface bodies is a different question. Not sure about that. |
Anders Hejlsberg (ahejlsberg)
commented
Oct 25, 2014
BTW, another difference between interfaces and type aliases is that interfaces support merged declarations whereas type aliases do not. |
Jason Freeman (JsonFreeman)
commented
Oct 25, 2014
Regarding local types in class and interface bodies: I would think this is only useful if you have a lot of private methods in your class, and they would benefit from the presence of a utility type. For example: classC{interfacePrivateType{// stuff}privatedoStuff1(p: PrivateType): PrivateType{
...
}privatedoStuff2(p: PrivateType): PrivateType{
...
}publicpublicMethod(p: PrivateType){// Error in --declaration modevarp2: PrivateType;// ok inside the method}}The idea is kind of like an existential type. The class knows about a type, that others need not know about, but it is useful to defined it internally to be shared by all its private methods. |
Jason Freeman (JsonFreeman)
commented
Oct 25, 2014
Code looks good, but let's finish addressing the remaining design questions before taking this. |
Anders Hejlsberg (ahejlsberg)
commented
Oct 25, 2014
Just pushed some changes to add support for type aliases in .d.ts files. I think we need Sheetal Nandi (@sheetalkamat) to look at the issue Ryan Cavanaugh (@RyanCavanaugh) points out above. Best I can tell the .d.ts generation code makes assumptions about scopes and visibility of named types that can be circumvented with type aliases. But a deeper issue is that when a declaration has a type annotation, the .d.ts generator doesn't emit what was in the annotation but rather emits from the resolved Type object. The latter is much harder to get right and doesn't "see" type aliases because they have already been resolved to the aliased type. For example: module M{exporttypeW=number|string;export module N{exportvarp: W;}}generates the following .d.ts file: declare module M{typeW=string|number;
module N{varp: string|number;}}Note that the type alias has been unrolled to the aliased type. I don't think we want that. Instead, when type annotations are present we should always emit them exactly as written, and we should only emit from Type objects when we're dealing with inferred types. |
Noel Abrahams (NoelAbrahams)
commented
Oct 26, 2014
I think the introduction of type aliases is unfortunate, since we now have two ways of declaring types: varfullName: {first: string;middle: string;last: string;};typeName=typeoffullName;// Same thinginterfaceName{first: string;middle: string;last: string;};Why not just cover the new scenarios introduced by union types and tuples using interfaces? interfaceStringOrNumberextendsstring| number {}interfacePointextends[number,number]{} |
Ryan Cavanaugh (RyanCavanaugh)
commented
Oct 26, 2014
There's actually a subtle distinction here. Interfaces are always "object types" (never primitives or union types), and declaring an Allowing an interface to extend a primitive type or a union type would open up a very weird can of worms. We'd have to drastically change what |
Anders Hejlsberg (ahejlsberg)
commented
Oct 26, 2014
Noel Abrahams (@NoelAbrahams) I think you have a valid point, but as Ryan Cavanaugh (@RyanCavanaugh) points out, primitive types and union types aren't object types and it simply isn't meaningful to extend them. The best we could do is to use the interfaceName{first: string;middle: string;last: string;}interfaceStringOrNumber=string|number;I'm sort of on the fence about it. I like that there isn't a new keyword, but I find it a bit odd to use |
Yahiko Uzumaki (yahiko00)
commented
Oct 26, 2014
Just as a suggestion, maybe replacing interface by type keyword: typeName{
first: string;
middle: string;
last: string;}typeStringOrNumber=string|number; |
Anders Hejlsberg (ahejlsberg)
commented
Oct 26, 2014
Yahiko Uzumaki (@yahiko00) Since we obviously can't take away the |
Noel Abrahams (NoelAbrahams)
commented
Oct 27, 2014
Anders Hejlsberg (@ahejlsberg)
that may very well be true. It's not something that seems to be terribly wrong IMO. Also, since the following is possible: interfaceMyStringextendsString{}varfoo : MyString='foo';// okaywhy not interfaceStringOrNumberextendsString|Number{}varfoo : StringOrNumber='foo'; |
Anders Hejlsberg (ahejlsberg)
commented
Oct 27, 2014
Noel Abrahams (@NoelAbrahams) It simply isn't meaningful to extend a union type, so we'd have to require the |
Jason Freeman (JsonFreeman)
commented
Oct 27, 2014
Noel Abrahams (@NoelAbrahams) In your example, if we keep the current meaning of extends, but allow it on union types, StringOrNumber would be equivalent to |
Ryan Cavanaugh (RyanCavanaugh)
commented
Oct 27, 2014
Documenting the endpoint of the very long conversation we had today spurred by Noel Abrahams (@NoelAbrahams)' suggestion of not adding a new keyword. Our approach was basically to talk only about the desired semantics of type aliases, and then decide the keyword based on what the behavior of those rules were. First, we want the following to be valid (disregard whether we use
We do not want this to be valid.
The reason for not wanting the last declaration is that someone might write The proposal we ended up with is that a type alias is only a valid declaration if it it takes one of the forms listed above (or an equivalent parenthesization thereof). We briefly discussed making Other basic things to note:
|
Jason Freeman (JsonFreeman)
commented
Oct 27, 2014
Ryan Cavanaugh (@RyanCavanaugh)'s comment: The type alias would also have to be in scope at the place we want to refer to it. If not, we may emit a privacy mismatch error. |
Anders Hejlsberg (ahejlsberg)
commented
Oct 28, 2014
Based on our discussion I just pushed a change that makes it an error to alias an object type literal: typePoint={// Error: Aliased type cannot be an object type literal. Use an interface declaration instead.x: number;y: number;};We decided to stick with the |
Dan Quirk (danquirk)
commented
Oct 28, 2014
What's the rationale for not emitting the alias if it was inferred? |
Noel Abrahams (NoelAbrahams)
commented
Oct 28, 2014
👍 thanks for considering the point. I think the proposed solution is a decent compromise. A clarification: in permitted case
is the type |
Anders Hejlsberg (ahejlsberg)
commented
Oct 28, 2014
Noel Abrahams (@NoelAbrahams) The |
Yahiko Uzumaki (yahiko00)
commented
Oct 28, 2014
This is probably a stupid question but are two named types sharing a same definition compatible? typeA=number[];functionfct(a: A){ ... }typeB=number[];varb:B;
...
fct(b);// is it a valid call? |
Ryan Cavanaugh (RyanCavanaugh)
commented
Oct 28, 2014
Yes. Type aliases won't affect compatibility in any way. |
Noel Abrahams (NoelAbrahams)
commented
Oct 28, 2014
Anders Hejlsberg (@ahejlsberg), Does permitting The role of |
Anders Hejlsberg (ahejlsberg)
commented
Oct 28, 2014
Noel Abrahams (@NoelAbrahams) The new thing is that you can declare aliases for types (but the aliases aren't actually a new kind of type). In your example above, referencing the alias |
Noel Abrahams (NoelAbrahams)
commented
Oct 28, 2014
Yes, thanks. That's quite clear now. I suppose for completeness the syntax needs to accommodate all cases, but from the perspective of how we write our code I think we might just discourage use of the following cases: types=typeofSomeClass;typep=string;typer=Date;since it doesn't make sense to call a spade anything but 😃 |
Type aliases allow names to be associated with arbitrary types.
Type aliases are particuarly useful for naming types that are not object types, such as union types or primitive types.
Type aliases can alias instantiations of generic types (as in the
NameLookuptype above), but they cannot themselves have type parameters.Just like interface declarations, type aliases declare only types and not values. For example:
An error occurs on
new StringList()because there is no constructor function named StringList. Only a type by that name exists. To get both, a subclass can be declared: