Skip to content
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

Replace instanceof checks to work across color.js sources #605

Merged
merged 11 commits into from
Nov 3, 2024

Conversation

MysteryBlokHed
Copy link
Member

Closes #602

Instead of relying solely on instanceof to check if an argument is an instance of some class (eg. Color or ColorSpace), also checks the name of the constructors of the instance to see if any of them have the same name as the target class.

This method isn't perfect, but it should work well enough given that it seems unlikely for a user of color.js to define their own unrelated class whose name conflicts with the library.

I'm open to changing this way of checking inheritance if anybody has better ideas.

Instead of relying solely on `instanceof` to check if an argument is an
instance of some class (eg. Color or ColorSpace), also checks the name
of the constructors of the instance to see if any of them have the same
name as the target class.

This method isn't perfect, but it should work well enough given that it
seems unlikely for a user of color.js to define their own unrelated
class whose name conflicts with the library.
Copy link

netlify bot commented Oct 28, 2024

Deploy Preview for colorjs ready!

Name Link
🔨 Latest commit b43cf4e
🔍 Latest deploy log https://app.netlify.com/sites/colorjs/deploys/67280523a656e30008d1129c
😎 Deploy Preview https://deploy-preview-605--colorjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Forgot to remove this after debugging.
Copy link
Member

@LeaVerou LeaVerou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks so much for working on this!

I love the idea of a generic isInstance() factory! Do we still need the public-facing APIs? Not sure we have authors that need to check if an object is a ColorSpace or a Format (I'd wager in most cases ColorSpace.get() would suffice). It seems more useful for Color since it does more than the instance check, though even there a static isColor() could be misconstrued as to also account for parseable color strings.

Since the API needs some more discussion, can we skip the public-facing APIs for now and just use private consts in each corresponding module? It seems in most cases the instanceof check is in the same module as the class it's checking, with one exception that can actually be refactored to a string check.

@@ -37,7 +37,7 @@ export default function getColor (color, options) {
// Object fixup
let space = color.space || color.spaceId;

if (!(space instanceof ColorSpace)) {
if (!ColorSpace.isColorSpace(space)) {
// Convert string id to color space object
color.space = ColorSpace.get(space);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could actually refactor this check to check if space is a string.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the util.isString() is "slow" and came up as a hotspot when I was running the texel benchmarks so I don't think we should add an additional call to isString().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting. Maybe just typeof space === "string" then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 0123830

@LeaVerou
Copy link
Member

Or actually — not use a const at all. Instead of isInstance() being a factory forcing people to call it in two steps, we could make that an implementation detail by using a WeakMap to associate constructors with their isInstance functions and every time it's called, we could check if we have already generated a function for this constructor, and if not, generate it and store it in the cache. Or, a regular object literal that keys the constructor name to the function.

Though wait a second, why are we generating functions at all? There is no one-time work that we are saving that way, it's literally function currying, that users of this function can do with function.bind().

src/color.js Outdated
static isColorInstance = util.isInstance(this);

static isColor (arg) {
return Color.isColorInstance(arg) || Array.isArray(arg?.coords);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the second part of the check should test to see if arg conforms to the PlainColorObject interface.

src/color.d.ts Outdated
@@ -159,6 +159,10 @@ declare class Color extends SpaceAccessors implements PlainColorObject {
| Record<string, DefineFunctionHybrid>
): void;

static isColorInstance (arg: any): arg is Color;

static isColor (arg: any): arg is (Color | { coords: Coords });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static isColor (arg: any): arg is (Color | { coords: Coords });
static isColor (arg: any): arg is (Color | PlainColorObject);

@lloydk
Copy link
Collaborator

lloydk commented Oct 28, 2024

I love the idea of a generic isInstance() factory! Do we still need the public-facing APIs? Not sure we have authors that need to check if an object is a ColorSpace or a Format (I'd wager in most cases ColorSpace.get() would suffice). It seems more useful for Color since it does more than the instance check, though even there a static isColor() could be misconstrued as to also account for parseable color strings.

I'm not convinced we need the static isColor() function mostly for the reason you mentioned.

If we think it may be useful then I think we should add a standalone function that checks to see if an object conforms to the PlainColorObject interface so that it can be used from the procedural API. Color.isColor() can then do the isInstance() check and call the new standalone function.

@MysteryBlokHed
Copy link
Member Author

MysteryBlokHed commented Oct 28, 2024

Though wait a second, why are we generating functions at all? There is no one-time work that we are saving that way, it's literally function currying, that users of this function can do with function.bind().

I wrote isInstance that way just as a convenience when adding the static methods for other classes—I didn't mean for it to be part of the public API that library consumers would use, since I figure it would be more intuitive for developers to call eg. ColorSpace.isColorSpace rather than something like isInstance(arg, ColorSpace).

But if it's unlikely that developers would need to use these static methods, then I'd be fine with rewriting isInstance to just be a regular function with two parameters.

@LeaVerou
Copy link
Member

Yeah, I think for now let's punt the static methods and just use IsInstance() directly.

Copy link
Collaborator

@lloydk lloydk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Checks if the space is a string instead of checking if it's not a
`ColorSpace` instance.
@LeaVerou
Copy link
Member

LeaVerou commented Nov 3, 2024

Is this ready for a re-review? Also, could you please add an entry to the changelog in the same PR?

@MysteryBlokHed
Copy link
Member Author

Is this ready for a re-review?

Should be ready now

Also, could you please add an entry to the changelog in the same PR?

Done!

Copy link
Member

@LeaVerou LeaVerou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, see comments on changelog. Thank you!

Co-authored-by: Lea Verou <lea@verou.me>
@MysteryBlokHed MysteryBlokHed merged commit 5c1536e into main Nov 3, 2024
5 checks passed
@MysteryBlokHed MysteryBlokHed deleted the replace-instanceof branch November 3, 2024 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OOP API should work with Color objects imported from a different Color.js URL
3 participants