-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Suggestion: Allow classes to implement type aliases #11207
Comments
I don't see any reason not to |
What does it mean to implement a union type? |
That would mean that the programmer at design time is required to make the class implement (at least) either of the constituents of the union type, so that it will fulfill the contract of the union type. |
Do you have a scenario in mind where this would be useful? |
As classes and types are clearly separated concepts in structurally types languages, I'd personally would like to use these respective conceptual tools in encapsulated forms. I would like to be able to directly define types (via type aliases) that I can use throughout my code, without being forced to use the old-school class/type-intertwined concept of interfaces. I don't see why it would be required for a type to be declared as either an interface or a class for us to use it as a way to guide design-time implementation of classes since TS is structural. So for me, this is more about having a cleaner conceptual model rather than being able to technically doing this or that easier. That is important for me as a developer, it makes my brain happier. |
What do you guys think? Do you think it's a reasonable proposal? |
If the target is something we would have let you |
I think the most interesting things to implement would be function types but there's no way of doing that with the classes especially given that the Still I do find myself wanting to implement arbitrary type declarations from time to time. It would be useful. |
Accepting PRs for this one -- allow the target of |
We currently use type aliases (which I like to call type definitions) to describe the structure of object literals (JSON objects used as DTOs) and interfaces to describe, well the interface for a class: type Foo ={
value: string;
};
interface Bar {
setFoo(foo: Foo): void;
}
class Bar implements Bar {} I don't think permitting classes to implement a type definition is a good idea, because it breaks this clean demarcation. When I see a type definition I can now immediately tell its purpose. But if type definitions are also being used to define behaviour then I loose that information. |
I'm not sure I get your point. I get that you use those differently, but why? Why would you want to have any demarcation here in the first place? Do you mean DTO's like in no behaviour? Then what do you think when you see this? type foo = {
bar(arg:string): void;
} |
yes. It's something we enforce through convention. So we wouldn't write that definition that you mention, because |
Ok, but then you are basically saying that this is not a good language design change because it somewhat interferes with your convention? |
I'm saying that there is no compelling reason to change the status quo. The justification for the requested change also appears to be with the view to supporting a convention: converting all interfaces into type aliases. Incidentally you may run into problems doing that because extending type aliases is a problem. Sent from my iPhone On 1 Nov 2016, at 22:26, Elephant-Vessel <[email protected]mailto:[email protected]> wrote: @NoelAbrahamshttps://github.com/NoelAbrahams Ok, but then you are basically saying that this is not a good language design change because it somewhat interferes with your convention? You are receiving this because you were mentioned. |
In my head, this is a matter of making the language more coherent and less complex, not about enabling a certain convention. Since ts is structurally typed, interfaces are really nothing more than design-time support for a developer to make sure that a class fulfills a certain contract. Types, irrespective of whether they are produced from classes, interfaces or type aliases, are used throughout the language with little discrimination. Except from the current constraint that classes only can implement types declared through interfaces. I think these kinds of constraints on a conceptual model, that inherently make the conceptual model more complex, needs to be properly justified. I cannot see how this constraint adds any value to motivate this complexity, and I'm sure we all have enough complexity in our life as programmers to feel stimulated anyway. I believe it's valuable to be able to think about only classes, objects and types, instead of classes, interfaces, objects and types, which you cannot do now. Using interfaces with classes could be optional instead of required. This is something that bothers me as a developer, I'm not just throwing random suggestions out there trying to be a language designer. I think the value justifies departing from status quo.
That could be expressed as an intersection type, or what do you mean? And to avoid misunderstandings, I'm not saying that interfaces should not be used. I'm only proposing that you shouldn't be forced to use them. |
I'm all for a simpler conceptual model. But that is actually a function of how complex a codebase is. For the work we do, I feel it is useful to have different semantics for type aliases in order to model JSON types. I have a feature request out for a 'json' keyword #1897 but that's not getting any love ATM
See #11961 for a difference between interfaces and type aliases. |
I like the idea to have some way to strongly separate objects of pure data and objects with behavior. And I'm sure that you do agree that type aliases isn't really the right tool for that.
I don't know... I agree it feels odd that the type of foo3 depends on the order of which the constituents of the intersection type are declared. And I also agree with the problem of interpreting what T[] & U[] actually mean, so maybe I'd rather have the compiler just not allowing those intersections in the first place. If someone is trying to have a class implement Interfaces gets around this problem as each extending type have to fulfill the extended type, so the compiler only have to care about the interface highest up in the chain. So interface T extends U (properties of U is a subset of the properties in T recursively) is not at all the same as T & U. |
I would think the following set of features and fixes would resolve some of the issues around type aliases and how they are currently used:
|
I am trying to create an "experience" (not sure what to call it) that matches chai's fluent chaining, where a member can be either a function or a step to another function. I have been able to define this as a mixin as described in http://www.typescriptlang.org/docs/handbook/advanced-types.html
I'd like to be able to implement factory in some way that didn't involve merging prototypes or similar monkey-patching mechanisms. |
@mattbishop there isn't an elegant way currently. The closest I know of is something like interface Identifier<T> {
id: T
}
type StringFactory = (value: string) => Identifier<string>;
type StringListFactory = (...values: string[]) => Identifier<string[]>;
type Factory<T extends StringFactory> = T & { list: StringListFactory };
const factory: Factory<StringFactory> = function () {
const factory = (v => ({ id: v })) as Factory<StringFactory>;
factory.list = (...args) => ({ id: args });
return factory;
}(); |
Thanks! I ended up following a similar pattern but had to use |
Well I use Anyway, it would really be nice if this could be done in a single expression. If we had Callable Class Constructors we could write const factory: Factory<StringFactory> = class {
call constructor(v) {return {id: v};}
static list(...values) {return {id: values};}
}; I'm not sure why the proposal was withdrawn. I read a note somewhere that said it was dropped because it could be accomplished using decorators instead. However, that sounds awkward. |
I think this one's already fixed by #13604. |
The
implements
-keyword in TS is used to guide the programmer of a class to ensure that the class is compatible with a certain (structural) type.The
interface
-keyword in TS defines a type, that except being a regular type, can be used design-time as a tool to ensure that a class is compatible with a (structural) type.Types other than those defined by
interface
orclass
can not be used with theimplements
-keyword.I suggest to drop this constraint to allow authors of classes to use (almost*) any type to guide the implementation of a class.
* Except types consisting of primitives (string, bool, number...), as these are nominal and cannot be implemented by a class.
(Sorry if this already have been discussed somewhere, but I can't find anything.)
The text was updated successfully, but these errors were encountered: