Determine if a value is Callable/Constructible

Caitlin Potter caitpotter88 at gmail.com
Mon Mar 30 12:36:29 UTC 2015


>On Mar 30, 2015, at 1:49 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> 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 <caitpotter88 at gmail.com> 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
> 
> 



More information about the es-discuss mailing list