Allen Wirfs-Brock (2013-07-17T00:13:17.000Z)
On Jul 16, 2013, at 4:10 PM, Erik Arvidsson wrote:

> All of the new constructors, Map, Set, WeakMap and WeakSet should fail
> when called as a function unless `this` already has a certain internal
> property, ie [[MapData]]. This is so that sub classing works as
> expected.

Yes, indeed: 
    let m = map();  //I think m is a map
is specified as throwing a TypeError.

To implement it otherwise is diverging from the spec.

Using constructors as callable factories is pretty hostile to subclasses that need to do super calls to superclass constructors.  While it is possible to carefully code a constructor function that it can work as both a factory and an initializer there are plenty of subtleties (and/or performance traps) that are likely to trip-up everyday JS programmers.  For that reason, I expect that callable constructors that allocate is going to be quickly identified as a ES6 anti-pattern.  The natural thing to do with ES6 classes is to always use new and to code the constructor function to only act as an instance initializer.  In support of that pattern, Map and friends only allocated when invoked via new.

We will be talking about this some more at next week's TC39 meeting.

> 
> However, all JS engines that supports Map, Set and WeakMap are future
> hostile and they all return a new instance when called as a function.
> For example, here is the V8 code:
> 
> function MapConstructor() {
>  if (%_IsConstructCall()) {
>    %MapInitialize(this);
>  } else {
>    return new $Map();
>  }
> }

Yes, this is wrong because if this was super called from a subclass constructor it would return a new Map instance to the subclass constructor rather than initializing (if possible) the subclass provided instance as Map. 

I'd don't know much about the V8 self-hosting mechanism, but it looks to me like it should be sufficient to code this as:

function MapConstructor() {
   %MapInitialize(this);
}

assuming that %MapInitialize is smart enough to throw on undefined and other this values that are not appropriate to initialize as Maps.

> 
> I understand that fully supporting @@create requires a lot of work but
> until then we should at least throw when called as function to allow
> us to get out of this mess eventually.

For background here is how new Foo()  is now supposed to work:
    let newObj = Foo[@@create]();  //allocate an initialized Foo instance.
    Foo.call(newObj);                         //initialize the newObject
)

It's not very complicated.

Allen


> 
> --
> erik
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130716/393150cc/attachment-0001.html>
domenic at domenicdenicola.com (2013-07-19T15:28:36.711Z)
On Jul 16, 2013, at 4:10 PM, Erik Arvidsson wrote:

> All of the new constructors, Map, Set, WeakMap and WeakSet should fail
> when called as a function unless `this` already has a certain internal
> property, ie [[MapData]]. This is so that sub classing works as
> expected.

Yes, indeed: 

```js
let m = Map();  //I think m is a map
```

is specified as throwing a `TypeError`.

To implement it otherwise is diverging from the spec.

Using constructors as callable factories is pretty hostile to subclasses that need to do super calls to superclass constructors.  While it is possible to carefully code a constructor function that it can work as both a factory and an initializer there are plenty of subtleties (and/or performance traps) that are likely to trip-up everyday JS programmers.  For that reason, I expect that callable constructors that allocate is going to be quickly identified as a ES6 anti-pattern.  The natural thing to do with ES6 classes is to always use new and to code the constructor function to only act as an instance initializer.  In support of that pattern, Map and friends only allocated when invoked via new.

We will be talking about this some more at next week's TC39 meeting.

> 
> However, all JS engines that supports Map, Set and WeakMap are future
> hostile and they all return a new instance when called as a function.
> For example, here is the V8 code:
> 
> ```js
> function MapConstructor() {
>  if (%_IsConstructCall()) {
>    %MapInitialize(this);
>  } else {
>    return new $Map();
>  }
> }
> ```

Yes, this is wrong because if this was super called from a subclass constructor it would return a new Map instance to the subclass constructor rather than initializing (if possible) the subclass provided instance as Map. 

I'd don't know much about the V8 self-hosting mechanism, but it looks to me like it should be sufficient to code this as:

```js
function MapConstructor() {
   %MapInitialize(this);
}
```

assuming that %MapInitialize is smart enough to throw on undefined and other this values that are not appropriate to initialize as Maps.

> I understand that fully supporting @@create requires a lot of work but
> until then we should at least throw when called as function to allow
> us to get out of this mess eventually.

For background here is how `new Foo()` is now supposed to work:

```js
let newObj = Foo[@@create]();  //allocate an initialized Foo instance.
Foo.call(newObj);                         //initialize the newObject
```

It's not very complicated.