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

clarify "is an object or function" #243

Open
donhatch opened this issue Dec 5, 2016 · 10 comments
Open

clarify "is an object or function" #243

donhatch opened this issue Dec 5, 2016 · 10 comments

Comments

@donhatch
Copy link

donhatch commented Dec 5, 2016

In "2.3.3. Otherwise, if x is an object or function",
it's not clear what "is an object or function" means.

My first guess was this:

(1) `x instanceof Object || x instanceof Function`

which seems to give the correct answer on values I tested it with.
However, apparently x instanceof Function implies x instanceof Object
(at least on values I tested it with), so the following seemed sufficient:

(2) `x instanceof Object`

However, the compliance test flunked that, complaining that my implementation
didn't examine x.then in some cases when it should have
(namely when "x is an object with null prototype").
So I tried switching from instanceof to typeof,
and after some experimenting came up with this:

(3) `typeof(x) === 'object' || typeof(x) === 'function'`

But the compliance test flunked that too, complaining that my implementation
behaved wrongly on x=null.
Finally I tried this:

(4) `x!==null && (typeof(x)==='object' || typeof(x)==='function')`

And finally the compliance test passes.

I don't think this was time well spent,
nor do I think it's tenable for the compliance test
to be the arbiter of what the spec means.

If the spec means exactly (4), it would be better if it would say that.

@bergus
Copy link

bergus commented Dec 6, 2016

It means "If Type(x) is Object", as we are talking about spec-level semantics here. There is no other meaning of "object" that could be relevant in this context.

The "or function" addendum was put there as a gentle reminder that all functions are objects as well, i.e. that the callability of x doesn't matter.

Your (4) expression is an implementation of the predicate, but not more than that. There are many other possible implementations, and none should be used in the specification.

@donhatch
Copy link
Author

donhatch commented Dec 6, 2016

@bergus, thanks for your reply and helpful links.

I know your wikipedia link was at least half-joking, but I do want to point out in seriousness that there are at least 3 different meanings of "object" in the context of javascript, and it doesn't look to me like the ECMAScript 2105 spec is very successful at sorting them out.

It means "If Type(x) is Object", as we are talking about spec-level semantics here. There is no other meaning of "object" that could be relevant in this context.

Perhaps that's true, but it's not at all obvious to me, even after you pointed it out and after I've gone over sections 4 and 6 of the ECMAScript spec to which you referred. In particular, my (1) and (2) still look like pretty good guesses to me, aside from the fact that the compliance test suite flunks them (which could actually be a bug in the test suite, for all I know).
I doubt I'm alone in this, among your intended audience of promise implementers.

Your (4) expression is an implementation of the predicate, but not more than that. There are many other possible implementations,

I'd be interested to see some of the "many other possible implementations" you have in mind, if you care to share. I only know of (4), and I got that by fumbling around in the dark til the test passed.

and none should be used in the specification.

Can you say more about why you feel that none of them should be mentioned? From my point of view, spelling out one of them for definiteness sure sounds like a good thing-- it would have saved me a substantial amount of time and aggravation.

@donhatch
Copy link
Author

donhatch commented Dec 6, 2016

Actually, my reading of Table 35 is that my (4) is not a valid test for the Object type after all, despite the fact that the compliance test suite passes it. The reason I believe (4) isn't valid is that, according to the last line of that table, if x is an "Object (non-standard exotic and does not implement [[Call]])" it might be that typeof(x) is "foobar". In that case (4) will wrongly conclude x is not an Object.
Please confirm.

@donhatch
Copy link
Author

donhatch commented Dec 6, 2016

In fact, it looks to me like Table 35 completely nails down the relationship between Object-ness and the typeof operator.
This appears to be more solid than I previously thought.

So, it looks to me like the following is a (the?) correct test for Object-ness:

x !== null &&
typeof x !== "undefined" &&
typeof x !== "boolean" &&
typeof x !== "number" &&
typeof x !== "symbol" &&
typeof x !== "string"

Assuming I have that right so far,
this is the only correct test for Object-ness that I know of at this time: my (1),(2),(3),(4) are all wrong, the compliance test suite's characterization is wrong, and I'll go out on a limb and say that I suspect some or all of the other "many possible implementations" that @bergus has in mind are wrong too. Of course I'm willing to be proven wrong :-)

It may be that there is an alternative implementation that uses instanceof in some way;
I don't know since I got lost trying to follow the discussion of instanceof in the ECMAScript spec.

@bergus
Copy link

bergus commented Dec 6, 2016

I had Object(o) === o in mind, which is the other (next to 4) idiomatic test for object-ness in JavaScript. That they differ about special host objects doesn't really matter, because host objects which overwrite their typeof behaviour don't want to be recognised as objects.

@donhatch
Copy link
Author

donhatch commented Dec 7, 2016

@bergus thanks for the example. Yes, it looks to me like Object(o)===o is a correct test, according to the MDN doc:

The Object constructor creates an object wrapper for the given value.
If the value is null or undefined, it will create and return an empty object,
otherwise, it will return an object of a Type that corresponds to the given value.
If the value is an object already, it will return the value.

More material here: http://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript . In particular, Oriol correctly points out that my proposed test using instanceof from above (2 comments ago), while correct, isn't as good as your Object(o)===o since mine will break if a future standard introduces a new primitive type.

Idiomatic (I guess that means "commonly used"?) or not, (4) is simply wrong according to the ECMAScript spec that you claim to be following.
Since you're insisting that promise/A+ spec's object means ECMAscript spec's notion of Object from part 6, and that's the only thing it could possibly mean, don't you think you should actually adhere to that definition? Otherwise it keeps looking like you're making up rules and exceptions as you go along
and then expecting people to read your mind.

That they differ about special host objects doesn't really matter, because host objects which overwrite their typeof behaviour don't want to be recognised as objects.

That sentence really doesn't hold up very well at all.
Two immediate objections:
(I) Such objects don't want that? Really? All of them? That's a bold thing to say, isn't it? I admit I'm not familiar with the context we're talking about, but it seems really odd to be making a claim about the wants of every object fitting this description that anyone might ever write, so I'm skeptical.
(II) If indeed you're right that some/most/all such objects don't want to be recognized as objects (which I read as "want to be treated as non-objects" per common English usage), then won't they be disappointed when your test Object(x)===x correctly identifies them as objects? Especially ones that also happen to be thenables, which will find that different promise libraries actually treat them differently.

Don't you think it would be better to actually follow and enforce the definition of Object that you're citing, instead of coming up with reasons to do some other thing based on presumptions of what all pathological objects want and shaky logic? The definition is actually looking fairly clear to me now, to my surprise.

I believe it's clear at this point that (4) simply fails to comply with the promises/A+ spec,
while the compliance suite lets it pass.
This inconsistency could be resolved in one of two ways:
(A) fix the compliance suite so that it correctly flunks (4), as required by the spec,
or,
(B) change the spec to allow (4), so that it will agree with the compliance suite.
I don't have a preference.

In any case, I'd also strongly recommend addressing the original issue, as follows:

  • Mention (perhaps even recommend) Object(x)===x. There are evidently lots of ways to do it right and lots of ways to do it wrong; mentioning this simple right way would be significantly helpful in guiding implementers toward success.
  • Also mention that "object" is being used as in the ECMAscript spec section 6. That's nowhere near as obvious as you've made it out to be, to many of the people of varying levels of javascript expertise who make up your audience. Referring to the ECMAscript spec would go a long way towards clearing the fog, as it did when you linked to it in this thread.

@bergus
Copy link

bergus commented Dec 7, 2016

These objects you are talking about are ignored because they're basically non-existent. They are absolutely non-standard, and while the ES spec does permit them it explicitly discourages implementations from using them.
This is also the reason why the compliance suite doesn't test them: there's no way to create such an object in node.js.

The only examples that come to my mind are document.all and some host objects from the IE7 era (like ActiveX), which were also known for not returning "function" despite being callable. Nobody gives a f*** about them, and none of them is thenable anyway.

The other reason why x != null && (typeof x == "object" || typeof x == "function") is commonly used over Object(x) === x is that it is significantly faster, not involving a global function call.

@donhatch
Copy link
Author

donhatch commented Dec 7, 2016

@bergus, thanks, good to know all these things.

Well, I guess flunking (4) isn't really an option then. In that case it would be great if the spec could be changed to match the suite. Not that I give a f*** about those objects, it's just that if there wasn't this apparent hole in the logic, it would have saved me from worrying or thinking much about it; likewise for others in my position in the future, and people out on the web who are trying to make sense of the spec.

In chrome: document.all.then = function(){}. Now it's a thenable. :-) Whatever.

Yeah, the lack of any efficient bulltetproof way to ask is-an-object is pretty bizarre.
And, isn't Object(x) more than just a function call-- it actually creates a new object in memory (in the case x wasn't an object), right? That sounds like quite a big deal for something that should be a lightweight check; but maybe it's not as big a deal in javascript as it is in C++, I'm not sure. On the other hand I suppose a javascript compiler/interpreter could optimize Object(x)===x into something that's even more efficient than x != null && (typeof x == "object" || typeof x == "function"), couldn't it?

@donhatch
Copy link
Author

donhatch commented Dec 8, 2016

Actually... it looks like document.all isn't an example of the last line of Table 35 for which typeof doesn't return "object".
From typeof doc on MDN:

All current browsers expose a non-standard host object document.all with type Undefined.
    typeof document.all === 'undefined';
Although the specification allows custom type tags for non-standard exotic objects,
it requires those type tags to be different from the predefined ones.
The case of document.all having type tag 'undefined' must be classified as an
exceptional violation of the rules.

So, as far as I know, neither nodejs nor chrome has any way of creating an object whose behavior is described by the last line of Table 35 and whose typeof doesn't return "object". Basically non-existent, as you say.

@gowpen
Copy link

gowpen commented Jan 25, 2018

I second @donhatch that this should be specified. The definition of thenable aims directly at specifying duck typing tests:

2.3. It introduces the term “thenable” as distinct from “promise”, so as to more precisely talk about the duck-typing tests necessary for implementation interoperation.

The definition uses this language:

1.2. “thenable” is an object or function that defines a then method.

Yet how to correctly detect "is an object or function" is left unclear. Referencing abstract operations in the ECMAScript spec is reasonable, and if that's the intention it should be explicit.

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

No branches or pull requests

3 participants