#do mixins work with generics?

30 messages · Page 1 of 1 (latest)

gusty pollen
#

I made a minimum reproduction of an error I'm encountering:

type Constructor<T = {}> = new (...args: any[]) => T
class Base {}

const myMixin = function <TBase extends Constructor>(Base: TBase) {
    return class myMixin<T> extends Base {
        
        doSomethingWith(value: T) {
            //
        }
    }
}

export class MyMixedClass<T> extends myMixin(Base)  {
}

Is this not possible? I get an error that's so counter-intuitive it comes across as a joke:

Class static side 'typeof MyMixedClass' incorrectly extends base class static side '{ prototype: myMixin<any>.myMixin<any>; } & typeof Base'.
Types of property 'prototype' are incompatible.
Type 'MyMixedClass<any>' is not assignable to type 'myMixin<any>.myMixin<any> & Base'.
Property 'doSomethingWith' is missing in type 'MyMixedClass<any>' but required in type 'myMixin<any>.myMixin<any>'.ts(2417)
main.ts(7, 9): 'doSomethingWith' is declared here.

It seems like what it's asking me to do (I didn't think for a second that this would work) is this:

constructor(...args: any[]) { super(...args) }

of course it doesn't work, because this isn't actually the problem. Does typescript see the generic as a complication of the constructor in some way? Is what I'm trying to do just not supported in typescript?

lusty flame
#

There are two "Ts," one from your final MyMixedClass, another from the myMixin(Base). Are they supposed to be the same thing?

finite crane
#

I managed to clear error, but not sure if its what u want

tropic atlasBOT
#
aurimasniekis#0

Preview:```ts
type Constructor<T = {}> = new (...args: any[]) => T
class Base {}

const myMixin = function <TBase extends Constructor>(
Base: TBase
) {
return class extends Base {
doSomethingWith<T>(value: T) {}
}
}

export class MyMixedClass<T> extends myMixin(Base)
...```

finite crane
#

or u can pass t to extend:
export class MyMixedClass<T> extends myMixin(Base)<T>

lusty flame
#

That's probably not what OP wants, since with that you can do:

new MyMixedClass<string>().doSomethingWith<number>(42)
gusty pollen
# tropic atlas

That works? How does typescript know what T is referring to in the mixin?

lusty flame
#

That T is a generic on the method itself, hence why I said it's probably not what you wanted.

finite crane
#

yeah its not correct solution

tropic atlasBOT
#
aurimasniekis#0

Preview:```ts
type Constructor<T = {}> = new (...args: any[]) => T
class Base {}

const myMixin = function <TBase extends Constructor>(
Base: TBase
) {
return class myMixin<T> extends Base {
doSomethingWith(value: T) {
//
}
}
}
...```

finite crane
#

but the mixin error of constructor still persists

gusty pollen
#

Oh I see, I was going to put it another way and say "if I had <A,B,C> on MyMixedClass how would TS know which to use as T"

#

Typescript sets my expectations really high and makes me feel like there should be a way to represent this

lusty flame
gusty pollen
#

yep, that sounds about right

#

I want the mixing to use the T that I specified in the class that's derived by applying my mixin to another class.

#

My context was I have a primitive that I came up with, I'll call it ObservableValue<T> here. There's reason to make different implementations of IObservableValue<T> but every implementation is going to have a common base set of functions IObservableValueLib<T> and also is going to implement the Pub/Sub interface. The Pub/Sub interface has a bunch of base functionality you would always want to implement (registering listeners in a linked list for efficient O(1) removal, etc) so it's beneficial to have a mixin. The PubSub interface is also a generic.

So basically I want to be able to make a CustomObservableValue<T> (any number of implementations of IObservableValue<T>), and I want PubSub<T> to be a mixin to give me my .pub and .sub methods.

#

In javascript, implementing this is trivial. In any typed language (apart from typescript) I would need to either violate the DRY principle (write redundant code), use up my one and only "what to inherit" card, or use a code generator to achieve this. In typescript... well I'm not sure yet, this is what I'm trying to figure out.

dry topaz
#

I think what's below is closer to what you want (it's honestly a bit abstract, still), but unfortunately, the params don't get passed around like that:

tropic atlasBOT
#
brunobernardino#0

Preview:```ts
type Constructor<T = {}> = new (...args: any[]) => T
class Base {}

const myMixin = function <
TBase extends Constructor,
T

(Base: TBase) {
return class extends Base {
doSomethingWith(value: T) {
console.log(value)
...```

dry topaz
#

I think you'll have to end up writing a class per "type", like:

tropic atlasBOT
#
brunobernardino#0

Preview:```ts
type Constructor<T = {}> = new (...args: any[]) => T
class Base {}

const myMixin = function <
TBase extends Constructor,
T

(Base: TBase) {
return class extends Base {
doSomethingWith(value: T) {
console.log(value)
...```

lusty flame
gusty pollen
#

Oh wow, I'm surprised this is broken, but astonished it's been broken for 5 years. This bug is kind of a big deal IMO, I literally can't use a mixin where I should be using one because of this, so I'm going to have to type redundant code everywhere to appease the type system...

lusty flame
#

You can, you just unfortunately have to use @ts- directive.

#

At worst you can always just cast the mixin to your desired return type.

tropic atlasBOT
#
type MyMixin<T> = {
    mixin(value: T): void
}

const myMixin = <A extends unknown[], I extends object>(
    Base: new (...args: A) => I,
): new <T>(...args: A) => I & MyMixin<T> =>
    class<T> extends (Base as any) implements MyMixin<T> {
        mixin(value: T) {}
    } as never

class Base {
    base() {}
}

class MyMixedClass<T> extends myMixin(Base)<T> {}

new MyMixedClass<string>().base()
new MyMixedClass<string>().mixin(42)
//                               ^^
// Argument of type 'number' is not assignable to parameter of type 'string'.
lusty flame
#

Here's a solution without @ts- directives. Not pretty, but it works.

#

(Personally I dislike mixin as a pattern, even beyond the spotty TS support)

#

The solution above has the unfortunate downside of having to repeat the type of MyMixin<T>. If you want to get rid of that, then an alternative would be to use @ts- directives: