-
Notifications
You must be signed in to change notification settings - Fork 89
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
What Are Idiomatic Brand Checks for Built-ins? #13
Comments
This might work well in combination with https://github.com/jasnell/proposal-istypes, which attempts to answer this question. |
In the library I wrote for pattern matching I used match(undefined, undefined) // true
match(true, true) // true
match(3, 12) // false
match(0, -0) // false
match(NaN, NaN) // true I found that to be generally useful, other builtin types I did more complicated things though: For functions I found the most useful default was that a function would const positive = x => x > 0
match(positive, 10) // true
function Positive() {
this.electrode = 'positive'
}
Positive[matchSymbol] = function() { return false }
match(Positive, 3) // false, Positive not even called For arrays I automatically converted them into an array-pattern check thing: match([1,2,3], [1,2,3]) // true
match([1,2], [1,3]) // false
match([[1], [2]], [[1], [2]]) // true
match([1, rest(arrayOf(number))], [1,2, 'cats', 3, 7])
// false, rest is just a special value I used to indicate that
// it should collect the rest then match against it For plain objects (e.g. with no prototype or Object.prototype prototype) I just did key value matching: const number = {
[matchSymbol](x) {
return typeof x === 'number'
}
}
match({ x: number, y: number }, { x: 10, y: 20 }) // true
match({ x: number }, { __proto__: { x: 10 } }) // true
match({ __proto__: { x: number } }, { x: 'Banana' } }) // false, only own enumerable keys used
match({ __proto__: null, x: 10, y: 20 }, { x: 10, y: 20 }) // true, but it was sufficiently rare a function would've been fine to do it In my ideal pattern matching it'd be fairly similar with objects/functions/arrays getting resolved automatically and having some way to pattern match rest of array: function matches(pattern, value) {
return match (value) {
pattern: true,
else: false
}
}
const Cheese = or('Brie', 'Camembert', 'Cheddar')
const Person = {
name: string,
age: number,
favouriteCheeses: [..Cheese],
bestFriend: or(null, person => matches(
Person, /* Using a function as definition is recursive */
person
)
}
const person = {
name: 'Bob',
age: 42,
favouriteCheeses: ['Potato']
}
matches(Person, person) // false |
I am also concerned about using In general though I think using constructors should be instanceof or |
I agree - that's why I don't want "constructors" as a special check. Rather, if you want |
It's still a shame there's no mechanism for built-in modules yet as then you could have a module with all the primitive checks (and potentially other useful patterns e.g. import { number, string } from patterns
const message = match (someValue) {
string: 'Is a string!',
number: 'Is a number!',
} It'd be nicer if they were just global but I don't think adding a bunch of a almost certainly commonly used variable names to the global scope would be a great idea. |
@Jamesernator they could be meta properties like Or perhaps for better syntactic distinction, |
The techniques described in https://esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-59 are probably of interest. That and subsequent posts in the thread also discuss the hazards of over generalizing the meaningfulness of such brand checks. |
That's precisely what brand checks are critically necessary for, and "try/catching around an arbitrary-for-each-type prototype method" isn't sufficiently usable or elegant to suffice. The only way a brand check isn't extremely useful for any object is if it has no internal slots or private state that a function could pivot on - the only builtin object type in JS that's true for is |
see: https://esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-61 In JS a brand check (however you perform it) of a non-frozen object guarantees very little about the object. It doesn't guarantee what its [[Prototype]] is. It doesn't guarantee what own properties it has. It doesn't guarantee anything about the actual behaviors of its methods. It doesn't tell you whether a Proxy has been used to change any of its essential internal method behaviors. It may be able to guarantee the presence of specific internal slots or private fields but it can't guarantee that they are actually used for anything. Unless, you are only branding/testing frozen objects it isn't clear what general invariant you think the test is guaranteeing. |
Since Proxy doesn't forward internal slots, it absolutely can tell you there's no Proxy involved - A brand check of a |
Hey y'all! #65 has gotten merged, and a lot of issues have become irrelevant or significantly changed in context. Because of the magnitude of changes and subtle differences in things that seem similar, we've decided to just nuke all existing issues so we can start fresh. Thank you so much for the contributions and discussions and feel free to create new issues if something seems to still be relevant, and link to the original, related issue so we can have a paper trail (but have the benefit of that clean slate anyway). There is some discussion to be had about the default |
The different types of brand checks I've identified in JS code follow these patterns:
It would be ideal if these were unified by pattern matching so that it is not necessary to use other mechanisms. Currently we tend to mix these in the same place which looks pretty messy. If we were to continue using the same brand checks we'd have to bail out of the pattern matching syntax to do so. That in turn means that whatever the default matchers are basically say what the best practice is.
One interpretation is that the proposal only defines the
x instanceof y
andy in x
. This means that this matching strategy is what we define as the best practice or common use case. However, this is not the cow path often used by libraries.For duck typing properties it is fairly uncommon to use
in
. It's common to usehasOwnProperty
to protect against prototype chains being tainted or expanded. Although it is also common to check for truthiness.For primitives it is more common to check
typeof x === "string"
thanx instanceof String
to ensure that you have a true primitive string rather than the object form. Aninstanceof
check mostly works when you only use primitives but it lets through cross-realm objects which can be bad since most code relies on the prototype methods being the same. It doesn't compose well when other parts of the system usetypeof
. It might be ok to try to push throughinstanceof
as the defacto way everyone should do it going forward, if we assume that the normal use case is to use primitive values in those cases.To be resilient against multiple realms, it is common to use
isArray
to allow arrays to pass between realms. Similarly, iframes have multiple documents and each of those have different Node and Element prototypes. Meaning thatinstanceof Element
is not safe across frames.node.nodeType === Node.ELEMENT_NODE
is used for such brand checks. Matching on Array or Element won't catch those in the current proposal. However, similarly to allowing cross-realm strings, it might also be confusing that they do match when the prototype may have different methods on it.Promise polyfills typically have a variant of
IsPromise
(which is not directly exposed by the standard library) to check for Promises for the same reason. Should matching a Promise doinstanceof
orIsPromise
?The text was updated successfully, but these errors were encountered: