Determining if an object can be constructed
Related: There's a proposal for Reflect.isConstructor
(and
Reflect.isCallable
) which addresses this very gap. There seem to be some
questions around Reflect.isCallable
(whether it should be true or false
for class constructors, since you can't [[Call]] them), but I didn't see
issues raised around Reflect.isConstructor
. Seems a bit stalled, though.
Le 16 janv. 2017 à 18:00, Michael Haufe <tno at thenewobjective.com> a écrit :
The question came up recently on CLJS [1] on how to determine if an object is safe to call as a Constructor.
The two following procedures will determine whether an object is a constructor without running it, according to ES6. (Whether it is safe to call it as constructor... you need first a definition of "safe".)
(1) Using class/extends
:
function isConstructor1(f) {
if (f === null)
return false
try {
class c extends f { }
return true
}
catch (e) {
return false
}
}
(2) Using Proxy
:
function isConstructor2(f) {
var p = new Proxy(f, { construct() { return {} } })
try {
new p
return true
}
catch (e) {
return false
}
}
Out of curiosity, why are classes specified to have a [[Call]] internal
method instead of special-casing typeof
and friends to work with them?
Somewhat of a tangent, but just a curious question on the design decision.
See my stackoverflow.com/questions/39334278/check-if-object-is-a-constructor-isconstructor
I also thought the extends
trick could be useful, but it fails 3 of my tests. It doesn't work for bounded normal functions and some proxy objects.
The proper way is the second one in Claude's post: using a proxy object.
There is only a problem: it doesn't work for revoked proxies, because they can't be used as a proxy target.
In my opinion that restriction is somewhat stupid, because you can use a revocable proxy as a proxy target, and revoke it later.
But I guess it's too late to change that. Maybe some day we will have Object.isConstructor
.
;Oriol
Le 17 janv. 2017 à 23:56, Oriol _ <oriol-bugzilla at hotmail.com> a écrit :
See my stackoverflow.com/questions/39334278/check-if-object-is-a-constructor-isconstructor, stackoverflow.com/questions/39334278/check-if-object-is-a-constructor-isconstructor
I also thought the
extends
trick could be useful, but it fails 3 of my tests. It doesn't work for bounded normal functions and some proxy objects.
Yes, I missed the fact that subclassing implies a concrete access to the prototype
property of the superclass in order to construct the subclass' prototype. That is the root of the failures.
The proper way is the second one in Claude's post: using a proxy object.
There is only a problem: it doesn't work for revoked proxies, because they can't be used as a proxy target.
In my opinion that restriction is somewhat stupid, because you can use a revocable proxy as a proxy target, and revoke it later.
But I guess it's too late to change that.
It is not too late to change that point, in that the risk of breakage may reasonably be judged almost inexistant. The hard part is to convince people that the inability to use a revoked proxy as proxy target is troublesome enough in order to justify the change. (Also, knowing why there is such a restriction may help to judge.)
Le 17 janv. 2017 à 23:48, Isiah Meadows <isiahmeadows at gmail.com> a écrit :
Out of curiosity, why are classes specified to have a [[Call]] internal method instead of special-casing
typeof
and friends to work with them? Somewhat of a tangent, but just a curious question on the design decision.
I guess that class constructors could have been specified without a [[Call]] internal method, with the cost of amending all the places where “constructible” implicitly implies “callable”. But why (and how) do you need “special-casing typeof
and friends”? (The way the question is formulated, avoiding special-casing would be an argument for the current design. But I miss what are the special cases you have in mind, especially regarding typeof
.)
Inline.
On Thu, Jan 19, 2017, 03:26 Claude Pache <claude.pache at gmail.com> wrote:
Le 17 janv. 2017 à 23:48, Isiah Meadows <isiahmeadows at gmail.com> a écrit :
Out of curiosity, why are classes specified to have a [[Call]] internal method instead of special-casing
typeof
and friends to work with them? Somewhat of a tangent, but just a curious question on the design decision.I guess that class constructors could have been specified without a [[Call]] internal method, with the cost of amending all the places where “constructible” implicitly implies “callable”.
FWIW, calling methods that don't have [[Call]] already throws a TypeError.
So it wouldn't affect necessarily all sites, especially if you continue to
check for [[Call]] in Array.prototype.forEach
(like what is currently
done), etc.
But why (and how) do you need “special-casing typeof
and friends”? (The
way the question is formulated, avoiding special-casing would be an argument for the current design. But I miss what are the special cases you have in mind, especially regarding
typeof
.)
I was specifically referring to typeof (class {}) === "function"
. The
"and friends" was in reference to things like the callback in
Array.prototype.forEach
, which IIUC doesn't currently throw for classes
if the array has no members.
Sorry for the poor phrasing there.
If you are looking for isClass or similar you can also rely on
Function.prototype.toString
decompilation, which is de facto consistent.
const isClass = (fn) =>
typeof fn === 'function' &&
!isClass.toString.call(fn).indexOf('class ');
Of course if you transpile code, including classes, that'd be useless (but then you'll have many other problems anyway)
Best
actually, let me make it faster against big functions ^_^
const isClass = (fn) =>
typeof fn === 'function' &&
/^class /.test(isClass.toString.call(fn));
Le 19 janv. 2017 à 13:18, Isiah Meadows <isiahmeadows at gmail.com> a écrit :
Inline.
On Thu, Jan 19, 2017, 03:26 Claude Pache <claude.pache at gmail.com <mailto:claude.pache at gmail.com>> wrote:
Le 17 janv. 2017 à 23:48, Isiah Meadows <isiahmeadows at gmail.com <mailto:isiahmeadows at gmail.com>> a écrit :
Out of curiosity, why are classes specified to have a [[Call]] internal method instead of special-casing
typeof
and friends to work with them? Somewhat of a tangent, but just a curious question on the design decision.I guess that class constructors could have been specified without a [[Call]] internal method, with the cost of amending all the places where “constructible” implicitly implies “callable”.
FWIW, calling methods that don't have [[Call]] already throws a TypeError. So it wouldn't affect necessarily all sites, especially if you continue to check for [[Call]] in
Array.prototype.forEach
(like what is currently done), etc.But why (and how) do you need “special-casing
typeof
and friends”? (The way the question is formulated, avoiding special-casing would be an argument for the current design. But I miss what are the special cases you have in mind, especially regardingtypeof
.)I was specifically referring to
typeof (class {}) === "function"
. The "and friends" was in reference to things like the callback inArray.prototype.forEach
, which IIUC doesn't currently throw for classes if the array has no members.
If you intend to have no observable difference, I don’t see the point to spec it another way just for introducing special cases here and there.
About typeof. If you insist that a class has no [[Call]] internal method, I think that typeof class {}
must not be "function"
. I have heard complaints that classes look like callable objects, but are not usable as callable objects in practice. It would not be nice to maintain that gotcha by an explicit special case.
Le 19 janv. 2017 à 13:46, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :
If you are looking for isClass or similar you can also rely on
Function.prototype.toString
decompilation, which is de facto consistent.
const isClass = (fn) => typeof fn === 'function' && !isClass.toString.call(fn).indexOf('class ');
Of course if you transpile code, including classes, that'd be useless (but then you'll have many other problems anyway)
It is also useless when Function#toString()
is not correctly implemented, which is currently the case for Firefox, see: bugzilla.mozilla.org/show_bug.cgi?id=1216630, bugzilla.mozilla.org/show_bug.cgi?id=1216630
Doh! Good point ... I didn't know Firefox was different from every other modern engine :-/
It's not, in that it has bugs like all of them. It's obviously unfortunate that the fix for this particular bug hasn't landed yet.
Michael Haufe <tno at thenewobjective.com> wrote:
The question came up recently on CLJS [1] on how to determine if an object is safe to call as a Constructor.
The original question was a response to
|| You cannot determine whether a function can be called as a constructor || unless you try it.
that asked the more specific question
| What would count as positive evidence that a function can actually be | called reasonably as a constructor function?
I was wondering if there was any way to distinguish a function like this
var fn = (function() {
var g = {};
return function() {return g;};
}());
which will pass all the proposed tests here, but still does not actually serve as a constructor function as is made clear by
// no errors here
var obj1 = new fn();
var obj2 = new fn();
obj1.foo = 'bar'
obj2; //=> {foo: 'bar'}
Without defining constructor functions explicitly, one would certainly hope that they would create new objects on each invocation, that there is some sort of difference between constructor functions and factory functions.
I wasn't really expecting there to be a positive answer. But I was curious and raised the issue. (For all I know, this is wandering into issues similar to the Halting Problem.)
But does anyone have a suggestion for a test that would reliably tell whether a function can serve as a true constructor in this manner?
I am unsure the exact question.
new
will allocate an object with a prototype for sure, but it is never
guaranteed to return that object even with classes.
let a = {};
class Foo {
constructor() {
return a;
}
}
const g = new Foo();
console.log(new Foo() === g);
If the question is "How can an external observer know if a constructor
returns this
vs some other object?" the answer is, you cannot.
It would be equivalent to solving halting problem.
Any function like "isConstructor" have to return three possible values - "yes" (native constructors, classes without constructor), "maybe" (can't determine), "no" (arrow functions, methods, generators).
a reflection on if and how "this" is used inside a function, would reveal what inner-interface it uses to some object, also also if it mutates its properties. every function mutating this-bound objects is potentiallyt a cinstructor. afaik:
new Foo(); is aequivalent to Foo.call(Object.create(Foo.prototype)) with an implicit "return this" at Foo"s end.
Bradley Meck wrote:
I am unsure the exact question.
I certainly agree that it's not a well-formulated question. Perhaps in the end it's not even coherent. But it seems to make some sense. I suspect that the answer is simply that it cannot be done. But I don't know how to demonstrate that.
new
will allocate an object with a prototype for sure, but it is never guaranteed to return that object even with classes.
let a = {}; class Foo { constructor() { return a; } } const g = new Foo(); console.log(new Foo() === g);
Which demonstrates that Foo
by such a criterion is not a reasonable
constructor function. I think that makes sense. It clearly isn't
one.
If the question is "How can an external observer know if a constructor returns
this
vs some other object?" the answer is, you cannot.
No, I don't think that's the central point. But perhaps the examples above do capture something fundamental. Even if we cannot prove it about an arbitrary function, that seems like a minimal criteria for a function to be a reasonable constructor:
A function,
Foo
can be used as a constructor function if
- every call to
new Foo(...args)
creates a distinct object not reference-equal to any other such call.- ??
Does that make sense? Are there other important criteria?
Of course this doesn't help us answer whether a particular function actually meets these criteria, but it's a start.
- every call to
new Foo(...args)
creates a distinct object not
reference-equal to any other such call.
Would this mean
function factory() { return []; }
would qualify as a constructor?
Bradley Meck wrote:
- every call to
new Foo(...args)
creates a distinct object not reference-equal to any other such call.Would this mean
function factory() { return []; }
would qualify as a constructor?
I'm thinking of that as a necessary, but not a sufficient condition.
If it fails that test it cannot be a constructor. It's clear that there are other tests. It's not clear to me what they are.
The reality here is that we're all playing around what the definition of "constructor" and "callable" mean.
In the spec, "constructors" are defined as having a [[Construct]]
internal slot; something is "callable" if it has a [[Call]]
internal
slot. By that definition (tc39.github.io/ecma262/#sec-isconstructor
and tc39.github.io/ecma262/#sec-iscallable), a class
constructor
is indeed callable (because it's defined to have [[Call]]
throw a
TypeError
). Similarly, as defined in the spec, typeof class Foo {}
is
"function" only because it has a [[Call]]
internal slot (
tc39.github.io/ecma262/#table-35); if constructors lacked a
[[Call]]
slot (such that IsConstructor
returned true
for them), then
typeof someConstructor
would return "object".
Obviously the committee could have chosen to define these things differently; but this is how things are defined.
I'm going to claim that conceptually - eg, to the majority of users -
something is a constructor when it's intended to be used with new
. In
other words, "what it returns" isn't actually the point - if you're "meant"
to use it with new
, it's a constructor, otherwise, it's a function - and
functions that work with both can be thought of as a dual
constructor/factory (the factory calls the constructor for you). However,
that conceptual definition is not one that can be programmatically
determined, because "programmer intention" in this regard simply isn't
inherently enshrined in an observable way, whether choosing class
or
function
.
Personally I think it would be wonderful to expose IsConstructor
(IsCallable
is already exposed via typeof
returning "function"), but I
feel quite confident that neither actually address any of the use cases in
this thread.
In addition, "safe to call" or "safe to use with new
" all depends on your
definition of "safe", and it's clear from this thread that that does not
have a universally agreed-upon definition either.
It might be useful to distill things down to 1) concrete use cases for calling or instantiating a function where you don't already know what it does, and can't assert in your human documentation what you expect; 2) a definition of "constructor", either a different one if you don't agree with mine above, or if you do, a form of mine that can be programmatically determined, and 3) a definition of "safe" which means more than "doesn't throw an exception".
I'll point out that all I need is some way to detect anything that is
callable except for classes. Basically, anything that has a [[Call]]
that
doesn't throw unconditionally without entering a different scope (like
classes, but not %ThrowTypeError%).
And to clarify, these would both be included per above, since it does enter a different scope:
// New language scope
function foo() {
throw new TypeError()
}
// New native scope (%ThrowTypeError%)
"use strict"
var foo = Object.getOwnPropertyDescriptor(
arguments, "callee"
).get
Reflect.isConstructor
isn't sufficient for my needs, since it would still
consider the first foo
above to be a constructor.
Unfortunately, the only practical way to know that without risking side effects is to read the human-produced documentation on the function, or to have a human read the code.
Actually it's possible to create pretty good heuristics to decide if it's possible to call function as constructor, but there is no way to be sure it's really safe.
To clarify, I'm wanting to know if it's a callable that isn't a class.
Technically, I could use Function.prototype.toString
for most
practical purposes of mine, but that'd be super slow (I'd need it in a
warm loop), and wouldn't catch native construct-only classes like Map
and Set
(which are still typeof f === "function"
).
var funcToString = Function.prototype.toString
function isCallable(f) {
return typeof f === "function" &&
/^\s*class(\s*\{|\s+[^(])/.test(funcToString.call(f))
}
Isiah Meadows me at isiahmeadows.com
as Claude already mentioned, that ain't going to work on Firefox due following bug: bugzilla.mozilla.org/show_bug.cgi?id=1216630
Le 24 janv. 2017 à 19:12, Isiah Meadows <isiahmeadows at gmail.com> a écrit :
To clarify, I'm wanting to know if it's a callable that isn't a class. Technically, I could use
Function.prototype.toString
for most practical purposes of mine, but that'd be super slow (I'd need it in a warm loop), and wouldn't catch native construct-only classes likeMap
andSet
(which are stilltypeof f === "function"
).
var funcToString = Function.prototype.toString function isCallable(f) { return typeof f === "function" && /^\s*class(\s*\{|\s+[^(])/.test(funcToString.call(f)) }
Your isCallable
function has a flaw: it will treat differently:
// ES6 class
class Foo {
constructor() {
// ...
}
}
and:
// pre-ES6 almost-compatible approximation of a class
function Foo() {
if (!(this instanceof Foo))
throw new TypeError("Foo must be invoked with 'new'");
// ...
}
You are unable to guess that a function defined with the function
keyword is intended to be a constructor or a function.
Yeah, I'm aware. I'm keeping my previous "prototype with a specific method" this time around. Much easier IMHO.
The question came up recently on CLJS on how to determine if an object is safe to call as a Constructor.
Traditionally one uses
try {... x = new foo() ... } catch(e) { ... x = foo() }
In the list of well-known symbols there doesn't seem to be a way to reference [[Construct]] to perform a test to avoid the try/catch. In the following though there is reference to Symbol.create which doesn't seem to be in the spec but seems to be the one to look for. Can we get clarification on if it is possible to determine if an object can be constructed before attempting it?