tannerntannern / ts-mixer

A small TypeScript library that provides tolerable Mixin functionality.
MIT License
379 stars 27 forks source link

Check if object's class has mixin #27

Closed LemonPi closed 4 years ago

LemonPi commented 4 years ago

Is there a way to check if an object uses a certain mixin (its class is defined with that mixin)? My use case for example would be

        this.map.entities().forEach((entity) => {
            if (hasMixin(entity, Actor)) {
                const actor = entity as unknown as Actor;
                // use as Actor
            }
        });

I could work around this by checking if the object has a certain function, but I would prefer a more general solution (since this solution would be specific to the methods of each mixin)

tannerntannern commented 4 years ago

Hi @LemonPi, so I believe the functionality you're looking for is custom instanceof behavior, which is currently listed as a "non-feature" of ts-mixer. The primary reason for this is that in order to define this custom behavior, ts-mixer would have to modify the source classes, which I would really like to avoid.

However, I might be able to add a hasMixin function to ts-mixer as a replacement for instanceof. It could also use type guards so that -- to use your example -- the type of entity would automatically be inferred as Actor simply by using hasMixin in the if statement...

Let me do some digging and I'll get back to you. 🙂

LemonPi commented 4 years ago

Ah interesting, I didn't know that you could implement custom instanceof behaviour with Symbol.hasInstance! And yeah, I think something "softer" would be just fine (it can just a call to a third party function rather than the instanceof operator).

One solution I can think of is if in the mixin module you have a map of base class to arrays of mixin classes that you populate inside calls to Mixin(...). Then the hasMixin function would just check if that mixin is inside the mapped array.

tannerntannern commented 4 years ago

This map solution you describe is exactly what I'm investigating at the moment. However, it is a little more complicated than just checking the mapped array, because the mixed class may be buried in the prototype chain, and the mixed class's constituents may also have mixed classes buried in their prototype chains.

LemonPi commented 4 years ago

Hmm things could get expensive with large prototype hierarchies... I'm currently using a heuristic way of checking that all properties of a mixin are in the prototype chain of the object somewhere. I'm not sure how the performance of this compares to prototype finding, but it also requires that I do the casting which is annoying.

tannerntannern commented 4 years ago

Well good news: you won't have to cast anymore because the hasMixin function already does the type narrowing for you. 5.3.0-beta.0 has been released, and you can try it out today with npm install ts-mixer@5.3.0-beta.0. The new README section covers it in more detail.

Let me know what you think! If you don't see any issues, I'll promote it to a full release.

Regarding your performance concern, my guess is that walking the prototype chain will be less intensive than checking for all properties, although I have not run the benchmarks to back this up. When you're working with mixins, prototype chains are often not very deep, but instead "wide", meaning the algorithm doesn't have to iterate very far to find what it's looking for. If someone has a very deep prototype chain with many layers of ts-mixer classes within, performance could suffer, but my guess is that it would be slight.

That being said, do let me know if you find the performance to be subpar, and I will see if I can improve my implementation.

LemonPi commented 4 years ago

Great, seems to work fine! The narrowing conversion removes even more boilerplate (and things that could get out of sync), which was my motivation for using this library in the first place :D. I haven't done any profiling, but I didn't see any dramatic difference in switching so it should be good to go 👍

tannerntannern commented 4 years ago

Glad to hear it works well for you. 5.3.0 is live. Thanks for your interest in ts-mixer and helping improve the library!