Determine if a value is Callable/Constructible

classic Classic list List threaded Threaded
43 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Determine if a value is Callable/Constructible

Caitlin Potter
**disclaimer** I know this has been brought up before, but bump :>

People are experimenting with polyfilled class implementations, which don’t all correctly throw when called as a function (no `new`). Eventually, they’re likely to be disappointed that this isn’t legal, and might have to undergo some serious pains to fix their applications.

I notice that this is particularly problematic for AngularJS, because classes are registered with an injector, which doesn’t know if it can `[[Call]]` them or not. It will later on try to `[[Call]]` (depending on how the class was registered with DI). It would be really great if we had a way to determine if this was going to throw or not, other than looking at the
stringified value of a function, so that these libraries could be updated to accomodate new class behaviour without pains (try/catch or processing Function.toString())

Some ideas:

Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins

I know it’s way too late for ES6, but maybe some kind of fast-tracked extension is in order? it should be pretty simple to implement these (and SM and v8 have variations of these in the runtime anyways)

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Brendan Eich-2
Last thread:
https://esdiscuss.org/topic/add-reflect-isconstructor-and-reflect-iscallable.
Died off again.

There's no issue with ES7 vs. ES6 without a spec, and having a draft
spec at the right stage (1 for flagged implementation?) is the thing.
Who will do it?

/be

Caitlin Potter wrote:

> **disclaimer** I know this has been brought up before, but bump :>
>
> People are experimenting with polyfilled class implementations, which don’t all correctly throw when called as a function (no `new`). Eventually, they’re likely to be disappointed that this isn’t legal, and might have to undergo some serious pains to fix their applications.
>
> I notice that this is particularly problematic for AngularJS, because classes are registered with an injector, which doesn’t know if it can `[[Call]]` them or not. It will later on try to `[[Call]]` (depending on how the class was registered with DI). It would be really great if we had a way to determine if this was going to throw or not, other than looking at the
> stringified value of a function, so that these libraries could be updated to accomodate new class behaviour without pains (try/catch or processing Function.toString())
>
> Some ideas:
>
> Reflect.isConstructor(fn) ->  true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
> Reflect.isCallable(fn) ->  true for pretty much any function, except for class constructors and a few builtins
>
> I know it’s way too late for ES6, but maybe some kind of fast-tracked extension is in order? it should be pretty simple to implement these (and SM and v8 have variations of these in the runtime anyways)
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Caitlin Potter
Well just to get the ball rolling, I’ve put together a markdown doc for this https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md

It’s such a minor item that I’m not sure how much to add to it, so maybe someone else will have a go at it instead.

(sorry for the spam)

On Mar 29, 2015, at 11:32 PM, Brendan Eich <[hidden email]> wrote:

Last thread: https://esdiscuss.org/topic/add-reflect-isconstructor-and-reflect-iscallable. Died off again.

There's no issue with ES7 vs. ES6 without a spec, and having a draft spec at the right stage (1 for flagged implementation?) is the thing. Who will do it?

/be

Caitlin Potter wrote:
**disclaimer** I know this has been brought up before, but bump :>

People are experimenting with polyfilled class implementations, which don’t all correctly throw when called as a function (no `new`). Eventually, they’re likely to be disappointed that this isn’t legal, and might have to undergo some serious pains to fix their applications.

I notice that this is particularly problematic for AngularJS, because classes are registered with an injector, which doesn’t know if it can `[[Call]]` them or not. It will later on try to `[[Call]]` (depending on how the class was registered with DI). It would be really great if we had a way to determine if this was going to throw or not, other than looking at the
stringified value of a function, so that these libraries could be updated to accomodate new class behaviour without pains (try/catch or processing Function.toString())

Some ideas:

Reflect.isConstructor(fn) ->  true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
Reflect.isCallable(fn) ->  true for pretty much any function, except for class constructors and a few builtins

I know it’s way too late for ES6, but maybe some kind of fast-tracked extension is in order? it should be pretty simple to implement these (and SM and v8 have variations of these in the runtime anyways)

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Brendan Eich-2
Caitlin Potter wrote:
> Well just to get the ball rolling, I’ve put together a markdown doc
> for this
> https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md 
>
>
> It’s such a minor item that I’m not sure how much to add to it, so
> maybe someone else will have a go at it instead.
>

Thanks, truly appreciated. These should get advanced at the next
meeting. We can jump them more than one step, to high stage even, after
everyone has a look and a night to sleep on them. If everyone on TC39
takes a look now, even better.

/be
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Allen Wirfs-Brock
In reply to this post by Caitlin Potter

> On Mar 29, 2015, at 11:51 PM, Caitlin Potter <[hidden email]> wrote:
>
> ...
>
> Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
> Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins

I’ve already seen another situation (node’s Buffer) where code could be simplified by using a ES6 class definition but where that is prevented because a class constructor throws when called.

Just to clarify something.  Class constructors actually are “callable”.  You can observe this by the fact that Proxy allows you to install an “apply” handler (the reification of the [[[Call]] internal method) on a class constructor.   The the fact that an object can be [[Call]]’ed is already reflected  by the typeof operator.  Class constructors throw when called because at the last minute we choose to make their [[Call]] do an explicit throw not because they aren’t callable.

There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.  

I think we should just drop that throws when called feature of class constructors..

(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

Allen


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Yehuda Katz
On Sun, Mar 29, 2015 at 10:49 PM, Allen Wirfs-Brock <[hidden email]> wrote:

> On Mar 29, 2015, at 11:51 PM, Caitlin Potter <[hidden email]> wrote:
>
> ...
>
> Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
> Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins

I’ve already seen another situation (node’s Buffer) where code could be simplified by using a ES6 class definition but where that is prevented because a class constructor throws when called.

Just to clarify something.  Class constructors actually are “callable”.  You can observe this by the fact that Proxy allows you to install an “apply” handler (the reification of the [[[Call]] internal method) on a class constructor.   The the fact that an object can be [[Call]]’ed is already reflected  by the typeof operator.  Class constructors throw when called because at the last minute we choose to make their [[Call]] do an explicit throw not because they aren’t callable.

There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.

I think we should just drop that throws when called feature of class constructors.. 

(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

I don't think this is an accurate representation of the discussion we had.
 

Allen


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
In reply to this post by Allen Wirfs-Brock
It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.



On 30 Mar 2015, at 07:49, Allen Wirfs-Brock <[hidden email]> wrote:


On Mar 29, 2015, at 11:51 PM, Caitlin Potter <[hidden email]> wrote:

...

Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins

I’ve already seen another situation (node’s Buffer) where code could be simplified by using a ES6 class definition but where that is prevented because a class constructor throws when called.

Just to clarify something.  Class constructors actually are “callable”.  You can observe this by the fact that Proxy allows you to install an “apply” handler (the reification of the [[[Call]] internal method) on a class constructor.   The the fact that an object can be [[Call]]’ed is already reflected  by the typeof operator.  Class constructors throw when called because at the last minute we choose to make their [[Call]] do an explicit throw not because they aren’t callable.

There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.  

I think we should just drop that throws when called feature of class constructors..

(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

Allen


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
In reply to this post by Yehuda Katz
There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.

I think we should just drop that throws when called feature of class constructors.. 

(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

I don't think this is an accurate representation of the discussion we had.

Any other reasons for throwing? It’d be great to know what they were!

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Allen Wirfs-Brock
In reply to this post by Yehuda Katz

On Mar 30, 2015, at 8:09 AM, Yehuda Katz <[hidden email]> wrote:

...

I don't think this is an accurate representation of the discussion we had.

It’s my characterization of the situation and reflects my position. I agreed to disabling calling class constructors via a throw in order to get the consensus necessary to move forward with finishing ES6.  However, I also think that that restriction was technically unnecessarily and crippling for some use cases.  I know you have some, as yet not fully specified, alternative in mind. I don’t know its details so I can’t directly comment on it. But,  I’m skeptical that of the need for anything other than new.target and I’m pretty sure that any alternative will be move complex and take longer to get into implementations.

Allen




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Allen Wirfs-Brock
In reply to this post by Axel Rauschmayer

> On Mar 30, 2015, at 8:40 AM, Axel Rauschmayer <[hidden email]> wrote:
>
> It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.
>
Can you give an example of what you mean?

Allen
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.

Can you give an example of what you mean?

```js
class MySuperClass {}

// This function assumes that MySuperClass is an ES5 constructor function
function MySubConstructor(foo) {
    MySuperClass.call(this);
    this.foo = foo;
}
MySubConstructor.prototype = Object.create(MySuperClass.prototype);
MySubConstructor.prototype.constructor = MySubConstructor;
```

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Allen Wirfs-Brock

> On Mar 30, 2015, at 10:12 AM, Axel Rauschmayer <[hidden email]> wrote:
>
>>> It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.
>>>
>> Can you give an example of what you mean?
>
> ```js
> class MySuperClass {}
>
> // This function assumes that MySuperClass is an ES5 constructor function
> function MySubConstructor(foo) {
>     MySuperClass.call(this);
>     this.foo = foo;
> }
> MySubConstructor.prototype = Object.create(MySuperClass.prototype);
> MySubConstructor.prototype.constructor = MySubConstructor;
> ```

so if MySuperCall didn’t have the throw on [[Call]] behavior the above would work just fine.

Allen

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
```js
class MySuperClass {}

// This function assumes that MySuperClass is an ES5 constructor function
function MySubConstructor(foo) {
   MySuperClass.call(this);
   this.foo = foo;
}
MySubConstructor.prototype = Object.create(MySuperClass.prototype);
MySubConstructor.prototype.constructor = MySubConstructor;
```

so if MySuperCall didn’t have the throw on [[Call]] behavior the above would work just fine.

In general, I’d expect this kind of subclassing to fail, due to the new instantiation protocol. Wrong?

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
In reply to this post by Caitlin Potter

On Mar 30, 2015 10:54 AM, Axel Rauschmayer <[hidden email]> wrote:
>>>
>>> ```js
>>> class MySuperClass {}
>>>
>>> // This function assumes that MySuperClass is an ES5 constructor function
>>> function MySubConstructor(foo) {
>>>    MySuperClass.call(this);
>>>    this.foo = foo;
>>> }
>>> MySubConstructor.prototype = Object.create(MySuperClass.prototype);
>>> MySubConstructor.prototype.constructor = MySubConstructor;
>>> ```
>>
>>
>> so if MySuperCall didn’t have the throw on [[Call]] behavior the above would work just fine.
>
>
> In general, I’d expect this kind of subclassing to fail, due to the new instantiation protocol. Wrong?

Would work fine if throw semantics removed and invoked as
```js
new MySubConstructor();
```
Just like ES5.

If invoked as
```js
MySubConstructor();
```
would fail just like ES5.

Throwing on [[Call]] is a compatibility hazard.


Don’t the different assumptions as to where the instance is allocated ever clash here? What if `MySuperClass` were:

```js
class MySuperClass extends Error {
}
```

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Claude Pache
In reply to this post by Allen Wirfs-Brock

Le 30 mars 2015 à 10:46, Allen Wirfs-Brock <[hidden email]> a écrit :


On Mar 30, 2015, at 10:12 AM, Axel Rauschmayer <[hidden email]> wrote:

It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.

Can you give an example of what you mean?

```js
class MySuperClass {}

// This function assumes that MySuperClass is an ES5 constructor function
function MySubConstructor(foo) {
   MySuperClass.call(this);
   this.foo = foo;
}
MySubConstructor.prototype = Object.create(MySuperClass.prototype);
MySubConstructor.prototype.constructor = MySubConstructor;
```

so if MySuperCall didn’t have the throw on [[Call]] behavior the above would work just fine.

Allen

I see an issue when MySuperClass contains itself a super() invocation... at least when that super-class is some builtin that doesn't support initialisation of pre-allocated instances, like `Array`. As currently specified, it will just throw, which is at least safe. It would be interesting to make it just work without hacks such as `if (new.target) super(); else super.constructor(...);`, and, in the same time, without silently break with `Array`, etc.

—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: Determine if a value is Callable/Constructible

Peter Flannery
In reply to this post by Caitlin Potter
>> On Mar 30, 2015, at 10:12 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
>> 
>>>> It doesn’t seem that big of a deal, but one risk is: people mistaking a class for a constructor, trying to subclass it as if it were a constructor and things failing silently.
>>>> 
>>> Can you give an example of what you mean?
>> 
>> ```js
>> class MySuperClass {}
>> 
>> // This function assumes that MySuperClass is an ES5 constructor function
>> function MySubConstructor(foo) {
>>    MySuperClass.call(this);
>>    this.foo = foo;
>> }
>> MySubConstructor.prototype = Object.create(MySuperClass.prototype);
>> MySubConstructor.prototype.constructor = MySubConstructor;
>> ```
> so if MySuperCall didn’t have the throw on [[Call]] behavior the above would work just fine.
> Allen

Would this be a work around? i'm seeing this work with v8 harmony classes enabled, or is this something that wont work eventually?
```js
function MySubConstructor(foo) {
  MySuperClass.constructor.call(this)
  this.foo = foo;
}
```

I also noticed that using apply to chain constructors won't work either as currently mentioned at Mozilla [Using apply to chain constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Example:_Using_apply_to_chain_constructors)

```js
// 
function applyConstructor(ctor, args) {
    var child = Object.create(ctor.prototype);
    var result = ctor.apply(child, args); // !!! throws Class constructors cannot be invoked without 'new'
    return result && Object(result) === result ? result : child;
}
```

but this still seems to work
```js
function applyConstructor(ctor, args) {
    return new (Function.prototype.bind.apply(ctor, [null].concat(args)));    
}
```

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Caitlin Potter
In reply to this post by Allen Wirfs-Brock
>On Mar 30, 2015, at 1:49 AM, Allen Wirfs-Brock <[hidden email]> wrote:

>There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way
>(new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.  

I don’t think it’s great to have branches in a constructor dealing with this — it’s not super-obvious reading the code what it means (so it’s another thing to train people to understand).

A better way (which I think has been suggested by someone already in a different thread), would be to have a separate “magic” method to provide `call` code.

```js
class Buffer {
  constructor(…a) {
    // …
  }

  factory(…a) { // [@@factory](), __factory__(), whatever
    return new Buffer(…a);
    // Or whatever else one might wish to do in a factory method
  }
}
```

But, I think the factory problem is solved well enough with static methods

```js
class Buffer {
  constructor(…a) {
    this.initialize(…a);
  }

  // Much easier to understand these, compared with Buffer(someBuffer) or Buffer(someArray) etc
  static withBuffer(buffer) { assert(Buffer.isBuffer(buffer)); return new Buffer(buffer); }
  static withArray(array) { assert(Array.isArray(array)); return new  Buffer(array); }
  static withSize(size) { assert(IsUInt(size)); return new Buffer(size); }
  static fromString(str, encoding = “utf8") { assert(IsString(str) && IsString(encoding)); return new Buffer(str, encoding); }

  initialize(…a) {
    switch (a.length) {
      case 1:
        if (IsUInt(a[0])) return allocateBufferOfSize(this, a[0]);
        else if (Array.isArray(a[0]) return allocateBufferFromArray(this, a[0]);
        else if (Buffer.isBuffer(a[0]) return allocateCopyOfBuffer(this, a[0]);
        else if (IsString(a[0]) { /* fall through */ }
        else ThrowTypeError(“Function called with incorrect arguments!");
      case 2:
        if (IsUndefined(a[1]) a[1] = “utf8”;
        if (IsString(a[0] && IsString(a[1]))  return allocateBufferFromString(this, a[0], a[1]);
      default:
        ThrowTypeError(“Function called with incorrect arguments!");
    }
  }
}
```

>I think we should just drop that throws when called feature of class constructors..
>
>(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to
>wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

I’m all for it if it can be allowed without making classes more complicated for consumers to use — The thing I like about requiring `new` is that it’s very simple and straight forward.

But in either case, these (IsCallable / IsConstructor) are pretty basic qualities of objects that a Reflection* api ought to be able to read into, imho.

>
>
>> On Mar 29, 2015, at 11:51 PM, Caitlin Potter <[hidden email]> wrote:
>>
>> ...
>>
>> Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
>> Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins
>
> I’ve already seen another situation (node’s Buffer) where code could be simplified by using a ES6 class definition but where that is prevented because a class constructor throws when called.
>
> Just to clarify something.  Class constructors actually are “callable”.  You can observe this by the fact that Proxy allows you to install an “apply” handler (the reification of the [[[Call]] internal method) on a class constructor.   The the fact that an object can be [[Call]]’ed is already reflected  by the typeof operator.  Class constructors throw when called because at the last minute we choose to make their [[Call]] do an explicit throw not because they aren’t callable.
>
> There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.  
>
> I think we should just drop that throws when called feature of class constructors..
>
> (The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)
>
> Allen
>
>

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Axel Rauschmayer
In reply to this post by Caitlin Potter

> Don’t the different assumptions as to where the instance is allocated ever clash here? What if `MySuperClass` were:
>
> ```js
> class MySuperClass extends Error {
> }
> ```


MySubClass preallocates when invoked via new. Just like ES5. So, ```MySuperClass.call(this)``` is same as ES5.  What happens in MySuperClass depends upon the ES6 level programmer.


Right, but I don’t see how an ES5-style constructor MySubContructor can allocate its instance and then have it initialized by an ES6 class (where the instance is allocated by a super-class). This is about ES5 code being confronted with ES6 code and assuming to see a constructor.

-- 
Dr. Axel Rauschmayer
[hidden email]
rauschma.de




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Tom Schuster
In reply to this post by Caitlin Potter
Thanks Caitlin for actually putting this onto github! I wasn't aware of that process when I posted about this to the mailinglist. Asking again from last time: Should we have Type == Object check like Reflect.isExtensible?

-Tom

On Sun, Mar 29, 2015 at 11:51 PM, Caitlin Potter <[hidden email]> wrote:
**disclaimer** I know this has been brought up before, but bump :>

People are experimenting with polyfilled class implementations, which don’t all correctly throw when called as a function (no `new`). Eventually, they’re likely to be disappointed that this isn’t legal, and might have to undergo some serious pains to fix their applications.

I notice that this is particularly problematic for AngularJS, because classes are registered with an injector, which doesn’t know if it can `[[Call]]` them or not. It will later on try to `[[Call]]` (depending on how the class was registered with DI). It would be really great if we had a way to determine if this was going to throw or not, other than looking at the
stringified value of a function, so that these libraries could be updated to accomodate new class behaviour without pains (try/catch or processing Function.toString())

Some ideas:

Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins

I know it’s way too late for ES6, but maybe some kind of fast-tracked extension is in order? it should be pretty simple to implement these (and SM and v8 have variations of these in the runtime anyways)

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Determine if a value is Callable/Constructible

Yehuda Katz
In reply to this post by Caitlin Potter
On Mon, Mar 30, 2015 at 5:36 AM, Caitlin Potter <[hidden email]> wrote:
>On Mar 30, 2015, at 1:49 AM, Allen Wirfs-Brock <[hidden email]> wrote:

>There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way
>(new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.

I don’t think it’s great to have branches in a constructor dealing with this — it’s not super-obvious reading the code what it means (so it’s another thing to train people to understand).

That's exactly my position. The co-mingling of [[Construct]] and [[Call]] in a single function was a side-effect of having all-singing, all-dancing functions. The nice thing about ES6 is that we got dedicated syntax for classes and callbacks. Just because we can make constructor functions serve double-duty via a reflective mechanism doesn't mean that's the right thing to do.
 
A better way (which I think has been suggested by someone already in a different thread), would be to have a separate “magic” method to provide `call` code.

```js
class Buffer {
  constructor(…a) {
    // …
  }

  factory(…a) { // [@@factory](), __factory__(), whatever
    return new Buffer(…a);
    // Or whatever else one might wish to do in a factory method
  }
}
```

That was the proposal I made that Allen alluded to:

```js
class Buffer {
  constructor(…a) {
    // …
  }

  [Symbol.call](a) {
    if (typeof a === 'string') {
      return Buffer.fromString(a);
    }

    return new Buffer(…arguments);
  }
}
```
 

But, I think the factory problem is solved well enough with static methods

```js
class Buffer {
  constructor(…a) {
    this.initialize(…a);
  }

  // Much easier to understand these, compared with Buffer(someBuffer) or Buffer(someArray) etc
  static withBuffer(buffer) { assert(Buffer.isBuffer(buffer)); return new Buffer(buffer); }
  static withArray(array) { assert(Array.isArray(array)); return new  Buffer(array); }
  static withSize(size) { assert(IsUInt(size)); return new Buffer(size); }
  static fromString(str, encoding = “utf8") { assert(IsString(str) && IsString(encoding)); return new Buffer(str, encoding); }

  initialize(…a) {
    switch (a.length) {
      case 1:
        if (IsUInt(a[0])) return allocateBufferOfSize(this, a[0]);
        else if (Array.isArray(a[0]) return allocateBufferFromArray(this, a[0]);
        else if (Buffer.isBuffer(a[0]) return allocateCopyOfBuffer(this, a[0]);
        else if (IsString(a[0]) { /* fall through */ }
        else ThrowTypeError(“Function called with incorrect arguments!");
      case 2:
        if (IsUndefined(a[1]) a[1] = “utf8”;
        if (IsString(a[0] && IsString(a[1]))  return allocateBufferFromString(this, a[0], a[1]);
      default:
        ThrowTypeError(“Function called with incorrect arguments!");
    }
  }
}
```

I agree that static methods are sufficient, but I also agree that it would be nice to be able to describe existing built-in APIs in terms of classes. That doesn't, however, mean that we need to force both use-cases into a single function called constructor.
 
I feel strongly that this:

```js
class Buffer {
  constructor(from) {
    // switch on Number, isArray, or Buffer
  }

  [Symbol.call](from, encoding='utf8') {
    if (typeof from === 'string') {
      return Buffer.fromString(from, encoding);
    }

    return new Buffer(from);
  }
}
```

is clearer than:

```js
class Buffer {
  constructor(from, encoding='utf8') {
    if (!new.target) {
      if (typeof from === 'string') {
        return Buffer.fromString(from, encoding);
      }
    }

    // switch on Number, isArray, or Buffer
  }
}
```

For one thing, it requires the reader to know that `new.target` is being used to determine whether the constructor was called with `new`. While it certainly is expressive enough, it's an unusual reflective operation that doesn't exactly say what you mean. For another, putting two uses into a single method and separating them by an `if` is quite often a hint that you want to break things up into two methods. I think that's the case here.

One of the nice things about the `[Symbol.call]` method is that a reader of the class can determine at a glance whether it handles [[Call]], and not have to scan the constructor to see if (and how!) `new.target` is used. And since `new.target` can also be used for other usages, a reader unfamiliar with the pattern might not even have a good query to Google (searching "what is new.target for in JavaScript", even if that works at all, might likely bring up a bunch of articles about implementing base classes).

>I think we should just drop that throws when called feature of class constructors..
>
>(The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to
>wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)

I'll bring up `[Symbol.call]` at the next meeting. It would be quite helpful if you would enumerate the areas in which you expect it to be complex, so I can make sure to address them in my proposal.
 
I’m all for it if it can be allowed without making classes more complicated for consumers to use — The thing I like about requiring `new` is that it’s very simple and straight forward.

The reason we dropped it was precisely because several of us felt that the cryptic `if (new.target)` check was a throwback to the original all-in-one design of functions, and that the new class syntax gives us the breathing room we need to describe things in a clear way without losing expressiveness.
 
But in either case, these (IsCallable / IsConstructor) are pretty basic qualities of objects that a Reflection* api ought to be able to read into, imho.

What Allen is saying is that the implementation of "throw if constructor" doesn't work by not implementing [[Call]], but rather by implementing [[Call]] to throw, so those reflective APIs would say the wrong thing, and that this is observable via proxies.

Allen, can you say more about why you spec'ed it that way?
 

>
>
>> On Mar 29, 2015, at 11:51 PM, Caitlin Potter <[hidden email]> wrote:
>>
>> ...
>>
>> Reflect.isConstructor(fn) -> true if Class constructor, generator, or legacy (and non-builtin) function syntactic form
>> Reflect.isCallable(fn) -> true for pretty much any function, except for class constructors and a few builtins
>
> I’ve already seen another situation (node’s Buffer) where code could be simplified by using a ES6 class definition but where that is prevented because a class constructor throws when called.
>
> Just to clarify something.  Class constructors actually are “callable”.  You can observe this by the fact that Proxy allows you to install an “apply” handler (the reification of the [[[Call]] internal method) on a class constructor.   The the fact that an object can be [[Call]]’ed is already reflected  by the typeof operator.  Class constructors throw when called because at the last minute we choose to make their [[Call]] do an explicit throw not because they aren’t callable.
>
> There is no intrinsic reason why we needed to mandate that class constructors should throw when called.  We even provided a simple and straight forward way (new.target===undefined) that a ES constructor body can use to determine whether it was called or new’ed.
>
> I think we should just drop that throws when called feature of class constructors..
>
> (The restriction was added to future proof for the possibility of inventing some other way to provide a class with distinct new/call behavior. I don’t think we need nor can afford to wait for the invention of a new mechanism which will inevitably be more complex than new.target, which we already have.)
>
> Allen
>
>

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
123