Repository navigation
Programatically Modifying Types #4490
Description
I see a challenge currently in TypeScript in supporting alternative methods of OO programming. The topic has come up in several forms before, and there isn't a clear solution. Here is a summary of related tickets all in a similar problem space:
- Mixin language support (with toy implementation) #2919 - Mixin language support (with toy implementation)
- Feature suggestion: treat prototype assignments like merging type declarations #4038 - Treat prototype assignments like merging type declarations
- Feature Request / Proposal: Traits #311 - Feature Request / Proposal: Traits
- Partial classes #563 - Partial Classes
- Proposal: structural overloading for interfaces #4302 - Proposal: structural overloading for interfaces
Currently, the only ways of "defining" types at design time:
- Interfaces - they support extension and are open ended
- Classes - supports traditional OO inheritance
- Abstract Classes - like Interfaces, but called Abstract Classes 😉
- Generics - Allows parameterisation of types
- Unions - Allow concatenation of types
- Intersections - More like a merge type actually 😉
In JavaScript, I can support other non-Inheritance and non-OO programming paradigms, but if I decide to extend those in TypeScript, I almost certainly will lose the static typing
For example, I can create a "composition/factory" in JavaScript, that makes it easy for me to "compose" functionality:
var compose = require('compose');
var factory = compose(function () { /* init */ }, {
foo: 'bar'
}, {
bar: 1
});
var a = factory.create();But if I wanted all sorts of TypeScript goodness, it essentially starts to get really complicated. I could of sort of used Generics with Intersections, but the actual definition of the type and the implementation are decoupled and users of the compose library would have to learn some "arbitrary" syntax.
Now if there was a way, behind the scenes, to programatically merge/mixin types and modify them, I could abstract the user from the implementation and it would just work... I would love to be able to write something like this in TypeScript:
import compose from 'compose';
class Foo {
foo: string = 'bar';
}
interface Bar {
bar: number;
}
let factory = compose(function () { /* init */ },
Foo,
Bar
);
let a = factory.create();Another example would be Aspect Oriented Programming. It is easy to support in JavaScript and actually the Decorators help a lot in TypeScript, but the challenge is that Decorators don't modify the type definition, and with after and around advice the return type can be modified. There is currently no way of expressing this in TypeScript.
There are probably loads of other patterns that are implemented in JavaScript which we cannot easily use in TypeScript and currently, it means us in the community are like a load of needy children, saying "I want, I want, I want, I need, I need, I need". Obviously a lot of this boils down to pseudo-religious debates, but without design time programmatic access to accessing and manipulating the types, we are going to constantly be running into these conflicts.
I am not sure of what the most logical and clean way the TypeScript team could implement something like this, but hopefully the team sees the need...
Activity
weswigham commentedon Aug 27, 2015
So... you can actually type your example fairly nicely in TS 1.6:
The only caveat is the arbitrary number of overloads - there's no way to say that it could take an arbitrary number of type arguments and "merge them all" using current syntax.
Similarly, there's no way to get the types of an array literal passed through the system. This literal:
Has the type
({a: number} | {b: string} | {doTheThing: () => void})[]. Strictly speaking, we could do better in this case if we remembered the arity of literals like this and associated each type with its slot in the tuple-like array. Considering we support tuple types in many places already, inferring this literal to be a tuple type may not be the worst idea. Giving it the type[{a: number}, {b: string}, {doTheThing: () => void}]seems way more accurate to me. If we could do that, then when I dowe know that the best overload is
compose<A, B, C>, rather than the present result: the inability to find a matching overload. Unfortunately I've been informed that there's some other issues with array-vs-tuple type uncertainty which can make it difficult for us to do that (what if the array is edited? Then its type changes. How do we know it changes? Ich.).Regardless - you can still do this:
(I personally think that it's cute that you can extract the type in that way, though it would be better if
typeofcould resolve a bit more than namespaces on its own)And have full types flowing throughout your code.
kitsonk commentedon Aug 28, 2015
Wesley Wigham (@weswigham) thanks! The overloading hadn't fully occurred to me. A couple of thoughts:
I think it has been discussed other places, but potentially a rest operator for generics is a part solution? Though how to operate it it programmatically would still need to be addressed unless it is implied a
...Rgeneric is always a intersection. Something like this:Another thought, extracting the type via
typeofis certainly useful. I think the challenge, I am seeing at the moment is while the extra "hoops" to jump through to extract the class information would be ok for some writing code, it become increasingly difficult for me to create a rational API I could reasonably get someone else to use (same to the challenges of the earlier of having to decide the arbitrary number of types).I mentioned Decorators above too, but I didn't give an example. Here is where I see things becoming difficult:
If I want a decorator to modify the type, I can't. I think the logic is the same for class decorators as well. It would be great if a decorator had some sort of ability to not only install functionality but change the type as well.
I guess what I am asking for is that now is to allow operators to work on type aliases... for example:
Then I can do all sort of "magic" including handling the merging of type, intersections, conflict resolution, decoration and manipulation without it having to be supported "natively" in TypeScript...
DanielRosenwasser commentedon Aug 28, 2015
Sounds like you'd want
$Eitheras well - check out #2710.kitsonk commentedon Jun 13, 2017
This really has been solved in lots of other ways. I don't have a legitimate use case anymore for this.