GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||
GeezerVison is showing github.com — Mixin classes by ahejlsberg · Pull Request #13743 · microsoft/TypeScript Conversation
|
|
I guess we can use multiple mixins this way?: class Customer extends Subscribable(Scorable(Tagged(Person))) { Everything fits good into exist language syntax, however it also would be great to have some syntax sugar mixins, maybe even separate keyword, because this does not look good between beautiful domain models and services: type Constructor<T> = new(...args: any[]) => T; function Tagged<T extends Constructor<{}>>(Base: T) { return class extends Base { And one another question, is it possible to do: function Tagged<T extends Constructor<{}>>(Base: T) { return class extends Base { /// ... } } class Customer extends Tagged(Person) { accountBalance: number; } let customer = new Customer("Joe"); and later on to check if |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Yes.
We may consider adding the
You can check if something is an instance of a specific invocation of const TaggedPoint = Tagged(Point); function check(obj: object) { if (obj instanceof TaggedPoint) { // obj narrowed to mixin type } } However, you can't use |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Anders Hejlsberg (@ahejlsberg) this is awesome, thanks for making this all work! Since #4890 was where we were talking about the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Umed Khudoiberdiev (@pleerock) it actually is possible to get I talk about this here: http://justinfagnani.com/2016/01/07/enhancing-mixins-with-decorator-functions/ though some members of the V8 team have warned me against using |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Justin Fagnani (@justinfagnani) We considered adding a Presumably users would expect We could of course consider having an So, the upshot of all of this is that any implementation of |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Anders Hejlsberg (@ahejlsberg) Thanks for the detailed explanation. There were some comments about deferring the class A { x: number; } // 1: is it possible to infer here that Base must be extendable by {x: string}? // extendableBy might be a new internal type operator? const B = <T extends Constructor<{}>>(Base: T) => class extends Base { x: string; } // 2: is possible here to check that A is extendable by {x: string}? // This is where the error would be reported const C = B(A); (by the way, this is why I was asking if we should open an issue, for this discussion. I can move it there, and it could be closed so I know it's over :) ) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
What would the types for a function which applied a mixin look like? I couldn't find a way to get this to work: type Constructor<T> = new(...args: any[]) => T; interface IMixin<S extends Constructor<{}>, T> { (superclass: S): T; // this return type should be Constructor<T> & S? } function applyMixin<S extends Constructor<{}>, T>(Mixin: IMixin<S, T>, SuperClass: S) { return Mixin(SuperClass); } class SuperClass { x() {} } function Mixin<T extends Constructor<{}>>(superclass: T) { return class extends superclass { y() {} }; } const A = applyMixin(Mixin, SuperClass); const a = new A(); // a.x is not known here // a.y is known here const B = Mixin(SuperClass); const b = new B(); // b.x is known here // b.y is known here |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Michael Jewell (@mjewell) That's an example of where we need higher-order functions (#9366), i.e. the ability to pass a generic function as an argument to another generic function without erasing the type parameters of the first function. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks for the quick response. Does this also mean there is no way to return a generic class from a mixin? Something like: class SuperClass<T> { } function Mixin<T extends Constructor<SuperClass>>(superclass: T) { return class<S> extends superclass<S> { y() {} }; } |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Wow. Is this really happening? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
ZhengMing Zhang (@zzmingo) I like it but I think class SuperHero mixin Person, CanFly, SuperStrength {} |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
We could also just do something like C#: class SuperHero extends Person, CanFly, SuperStrength {} Would work just about the same. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Grant Eagon (@granteagon) I don't think the TypeScript team wants to add new additional features outside of he type system. There's too much danger of incompatibilities with future JavaScript evolution. FWIW, I have a proposal to TC39 to add mixins to JavaScript: https://github.com/justinfagnani/proposal-mixins As that progresses, the TypeScript team could consider adding support. I'm not sure when they start adding proposed features, but probably not earlier than stage 3. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Justin Fagnani (@justinfagnani) Mixins seem to solve a lot of inflexibility with OOP. They also somewhat bring the functional programming world and OOP world closer together. If there's a way I can show support for you proposal, let me know. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Hello everyone, I'm trying to figure out how to type a class-factory. I've an API that takes an object literal, and effectively returns the equivalent of a class. const Foo = Class(({Private}) => ({ someMethod() { Private(this).somePrivateMethod() }, private: { somePrivateMethod() { ... }, } })) const foo = new Foo foo.someMethod() where I made a StackOverflow question about it, and I figured to share it here because the people that know most about class factories in TypeScript are right here. :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Joe Pea (@trusktr) Have not yet think deeply in how to solve your problem, but you might encounter this: #17388 when you create your solution. 🌷 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
In my opinion one of the issues of classes in javascript is absence of mixins. People can do object merging and destructing with plain javascript objects and lot of people simply use factory functions which produce pojos and have all javascript flexibility. Although it is possible to do mixins right now (this PR) but usage is ugly and probably is used by people on some edge case scenarios. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I agree, using TypeScript adds a lot of complexity when trying to do stuff like this that is otherwise simple in plain JavaScript. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Seems like we are getting off topic. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Joe Pea (@trusktr) there is a little known typescript type mostly because its undocumented that will allow you type that called ThisType And all i solved the Mixin problem today in a correctly typed implementation. The underlying problem is that the "this" of a class cannot be changed unless via a super-type or itself, this limits and solution because what you want is essentially to manipulate the type of "this" within the class. EDIT: Thought i'd intercept here but i know you can manipulate the "this" within a function but i'm talking about within classes as a whole. Understanding that i wrote this with some help. And the implementation..... I feel like this should put this request to rest, it doesn't need special syntax or some hieroglyphics solution that hacks together over complicated typings. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
i can't believe that this adventure is officially called "mixins":
and all this is happening in a broad daylight when much better results can be achieved in plain JS by simply calling a bunch of initializers on a bare object this is a notorious case of where "support idiomatic javascript code" goal is violated so does typescript have mixins? no it does not |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I did an adaptation of Shanon Jackson (@ShanonJackson)'s solution and got a pseudo form of generics working. Daniel West (@JustASquid) Aleksey-Bykov I have the same issue as you guys and this seems to be the closest thing I got to generics. This seems to be somewhat "okay" in terms of being able to have stable classes and mixing those in - instead of defining mixin functions with anonymous classes. export type Constructor<T = {}> = new (...args: any[]) => T; /* turns A | B | C into A & B & C */ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never /* merges constructor types - self explanatory */ type MergeConstructorTypes<T extends Array<Constructor<any>>> = UnionToIntersection<InstanceType<T[number]>>; export function Mixin<C1>(ctor1: Constructor<C1>): Constructor<C1>; export function Mixin<C1, C2>( ctor1: Constructor<C1>, ctor2: Constructor<C2>, ): Constructor<C1 & C2>; export function Mixin<C1, C2, C3>( ctor1: Constructor<C1>, ctor2: Constructor<C2>, ctor3: Constructor<C3>, ): Constructor<C1 & C2 & C3>; export function Mixin<C1, C2, C3>( ctor1: Constructor<C1>, ctor2: Constructor<C2>, ctor3: Constructor<C3>, ): Constructor<C1 & C2 & C3>; export function Mixin<C1, C2, C3, C4>( ctor1: Constructor<C1>, ctor2: Constructor<C2>, ctor3: Constructor<C3>, ctor4: Constructor<C4>, ): Constructor<C1 & C2 & C3 & C4>; export function Mixin<C1, C2, C3, C4, C5>( ctor1: Constructor<C1>, ctor2: Constructor<C2>, ctor3: Constructor<C3>, ctor4: Constructor<C4>, ctor5: Constructor<C5>, ): Constructor<C1 & C2 & C3 & C4 & C5>; export function Mixin() { const constructors = [].slice.call(arguments); return constructors.reduce((cls, mixin, idx) => { if (!idx) { return mixin; } const mixedClass = class extends mixin { constructor(...args: any[]) { super(...args); cls.call(this, args); } }; Object.assign(mixedClass.prototype, cls.prototype, mixin.prototype); return mixedClass; }); } Example: class A<T> { a = 1; b = 3; genericFuncFromA(some: T) { } } class B { c = 2; duck() { } } class E { d = 4; quack() { } } class C<T> extends Mixin(A, B, E) { something(testing: T) { } }; // retype the generic classes you care about type _C<T> = C<T> & A<T>; const d: _C<string> = new C<string>(); All the instance variables/methods come through great: The generics are somewhat working...: Can't get out of the single base limitation either. Anyone might have ideas to how this could be achieved? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
its also easily possible to adapt the solution to use Objects instead of classes, or both. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I implemented a solution to the generics problem in my ts-mixer library, but the solution can easily be adapted if you don't want to take on a (albeit, small) dependency. Essentially, the trick is to use class decorators (which can't alter types) to apply the mixins "on the JavaScript side" in conjunction with interface merging to coerce the proper class type "on the TypeScript side." Because TypeScript is blind to changes made by class decorators, the interface merging will work without conflicts: import {MixinDecorator} from 'ts-mixer'; // Some generic classes class GenericClassA<T> { testA(input: T) {} } class GenericClassB<T> { testB(input: T) {} } // Class decorator/interface merging trick to create the generic mixed class @MixinDecorator(GenericClassA, GenericClassB) class Mixed<A, B> { newMethod(a: A, b: B) {} } interface Mixed<A, B> extends GenericClassA<A>, GenericClassB<B> {} let mm = new Mixed<string, number>(); mm.testA('test'); // ok // mm.testA(2); // will cause error mm.testB(2); // ok // mm.testB('test'); // will cause error More info on this exploit is available in the ts-mixer docs. If you prefer not to use my library, a potential function MixinDecorator(...constructors) { return function<T, U extends T>(baseClass: T): U { // `Mixin` is assumed to come from one of the solutions above. return class Mixed extends Mixin(baseClass, ...constructors) {} as unknown as U; }; } I'd love feedback if anyone has any. 🙂 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Anders Hejlsberg (@ahejlsberg), I am the author of @northscaler/mutrait, which enables stateful traits in JavaScript. I'm trying to use what this PR enables in order to implement a similar thing in TypeScript, but I'm still not sure if the language's type system permits it. I have a github repo demonstrating the issue at https://github.com/matthewadams/typescript-trait-test. TL;DR: I'm basically trying to enable a simple way for folks to define traits & enable their classes to express them, like This leverages the subclass factory pattern you describe at the top of this issue, but attempts to do so in a way that allows a class to override functionality. Your example illustrates that you can add functionality to a class, but you can't customize it easily because the subclass factory-created class extends the class receiving the functionality, instead of the other way around. This works fine in JavaScript using If you Thanks in advance for this. Because we leverage traits in many of our projects, this is preventing us from using TypeScript more often. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Intersection type containing a constructor function type should count as one Allow class to extend from a generic type parameter
There was an error while loading. Please reload this page.