Good-bye constructor functions?

Herby Vojčík herby at mailbox.sk
Wed Jan 9 01:51:14 PST 2013


Sorry if I was too dense in my reply so things weren't understood. I
don't know how to reply put things efficiently because without big
picture, isolated cherry-picks seem ridiculous and properly put big
picture is often tl;dr-ed.

Concrete replies below.

Allen Wirfs-Brock wrote:
> On Jan 8, 2013, at 12:45 AM, Herby Vojčík wrote:
>
>>
>> Allen Wirfs-Brock wrote:
>>> On Jan 7, 2013, at 4:09 PM, Herby Vojčík wrote:
>>>
>>>> ...
>>> I just don't see such an inconsistency in the current ES6 spec.
>>> draft. A constructor is just a function that is usually called
>>> with a this value. This is true whether it is called by the
>>> [[Constructor]] internal method or via a super/super.constructor
>>> call. In either case, the primary purpose of the constructor is
>>> to perform initialization actions upon the this value. Where is
>>> the inconsistency?
>> (I claim that) in any circumstances, what developers want to
>> express when writing `super(...args)` in the constructoro of
>> SubFoo, is: "On my this, which is now instance of SubFoo, I want
>> the identical initialization code to be run, which `new
>> Foo(...args)` would run to initialize newly created instance of
>> Foo"
>>
>> That's not true. Because the spec is trying to serve two masters:
>> F and F.prototype.constructor. It is impossible.
>>
>> The fixed semantics of [[Construct]] for `class` ((1) above)  is
>> fixing this by only serving one master: F.prototype.constructor
>> (in line 3).
>
> I agree with your above statement about initialization.  But I also
> content that is exactly what the current specification of super does
> within a constructor function (subject, of course to what the
> invoked methods actually are coded to do).  What I don't see  is why
> you

What's it? super does call the .prototype.constructor of superclass,
yes, I know that well, ...
> think otherwise.  I need a clearer concrete explanation of what you
> see is the problem, prefably without a forward reference to what you
> think is the solution.
...but new does not call the .prototype.constructor.

There this does not hold for `super(...args)` behaviour:
"On my this, which is now instance of SubFoo, I want the identical
initialization code to be run, which `new Foo(...args)` would run to
initialize newly created instance of Foo".

And if you argue they are identical at the beginning, I say they can be
desynchronized, they will, and it does not matter how is the default case.

This state of serving two masters (new F, super F.prototype.constructor)
is a design issue / inconsistency / bug in the core of the language.

And sorry if I mention the aolution (it is simply "call
.prototype.constructor" in new for `class`), but it saves the model of
super-without-special-cases for constructor, which is fine (special
cases aren't).

>>> The only anomaly I see is that a handful of legacy built-ins do
>>> completely different things for new operator originated calls in
>>>  contrast to regular function calls. There is no confusion in
>>> the spec. about this and the mechanism for accomplishing it.
>>> However such split behavior can't be declaratively defined in a
>>> normal function or class declaration. In the other thread at
>>> https://mail.mozilla.org/pipermail/es-discuss/2013-January/027864.html
>>>
>>>
I described how this split behavior an be procedurally described
>>> in such declarations and also described how the same technique
>>> can be applied to the offending built-in constructors (r any
>>> user defined class constructor) to discriminate between
>>> initialization and "called" behavior, even when called via
>>> super.
>> Yes, but it is a workaround.
>
> Or, alternatively stated, it shows how the objective can be met
> without any further complicating the actual language.   That is
> arguably a good thing, not just a "woprkaround"

Taken ad absurdum, JS is turing complete, you can achieve anything
without complicating the actual language.

>>> The only "fix" I saw that you made to super was that in you
>>> sketch constructor methods defined via a class definition are
>>> only used for instance initialization, so there doesn't need to
>>> be any code to determine between initialization and call
>>> behavior. Constructor behavior is always initialization
>>> behavior.
>> Yes, it is there. As a free bonus, btw. The main druver for the
>> design was new/super two masters fixing. Then, later (Brendan Eich
>> will not like this ;-) ) the beauty of breaking the tight coupling
>> and not needing [[Call]] at all for object with [[Construct]].
>>
>> There are in fact TWO freebies: - [[Call]] separated from
>> [[Construct]] - you get default constructor for free; by not
>> defining it explicitly.
>
> Sorry, I still don't see it, you have to explain both the problem
> and your solution more concretely.

You probably see [[Call]] / [[Construct]] separation - class object's
[[Call]] is orthogonal to what [[Construct]] does, since it is specified
to call .prototype.constructor. It follows naturally from the fact that
class !== constructor.

So the issue needing explanation is "free default constructor", I
presume (tell me if I am wrong).

It goes this way: if the [[Construct]] has proposed semantics of,
roughly, `[[Call]]([[Get]](.prototype,"constructor"))`, two things can
happen:
   - there is an own "constructor" in .prototype, in is result of [[Get]]
and is [[Call]]ed
   - there is not an own "constructor", so inheritede one is return from
[[Get]] and [[Call]]ed

The second point is equivalent to the case if there was an own
constructor with body `constructor(...args){return super(...args)}`
which is the same (sans return) that what you propose to explicitly
define as the default constructor.

IOW, what you propose as the default constructor is explicit definition
of "I don't have any constructor given, just call the inherited one".

The proposed semantics of [[Construct]] does that naturally, without
needless explicit constructor.

In fact I deem this a feature - you can always tell a class to use
inherited constructor by making it not have an own one (and not only
when defining it, but in runtime as well).

>> When such freebies appear, for me this is the signal that
>> someth8ing "clicks", eg., the design matches requirements very
>> good.
>>
>>> However, you don't support non-new invocation behavior, at all,
>>> on class objects so you can't use a class to self-host an
>>> implementation of, for example, RegExp. You also, don't do any
>>> thing to support correct
>> Read the rest of the reply, please. The exact contrary is in fact
>> true. I do. (meta question: why it is so often here that people
>> are cutting the rest and reply only on the first line dismissing
>> the rest?) The proposal was first step: do class as plain objrct
>> with [[Construct]], thereby blessing class/constructor separation.
>> The second step is: when plain object can do, anything can do (the
>> only question is how to specify it).
>>
>> It was sketched with more examples in the rest.
>
> I did read the rest.  I don't see any provisions for calling the
> class object.  Can you point me concretely  to where you define it?

THe proposal of "separate the class and constructor, allow class to be
ordinary object" is the first step. The actual separation of the fused
concepts. In the proposal itself, I proposed the class being the
ordinary object with [[Construct]] of its own calling
.prototype.constructor.

The sketches of second step was in the reply, they go on the line "if
ordinary object can do, anything else can do; I want to be able to
specify it". Concrete things below.

I wrote:
> *There is more than narrow technical PoV.  By returning plain object
> with exotic [[Construct]], working in nearly every detail as a class,
> while affording not to be the constructor itself, you effective say
> openly "You can use any [[Construct]]ified object as a class in ES6+.
> Gates are finally open".

Here, I say there is no need to stick to plain object for `class`, but 
other objects could be allowed (ordinary object being just the default 
of the `class` construct).

> It can be blessed by Reflect.makeClass(classObject, protoObject) or
> similar API.

Here, I propose there is an API for this. My mistake was that I did not 
explain its semantics.

I want `Reflect.makeClass(cls, proto)` to do this:

   1. Object.define(cls, "prototype", {value: proto, c:false, w: false, 
e:false}).
   2. Define `class` [[Construct]] on cls (one that calls 
.prototype.constructor).
   3. Return cls. // not needed, really.

OTOH, do the same thing that make an orfinaty object a class in my 
proposal - define its .prototype and [[Construct]].

> For example, the criticized pattern of 'function that does
> differently for [[Call]] and [[Construct]]' could now be created as
> well. Just 'makeClass' it.

Here I say one can create a `class`-like construct that has its 
[[Call]], orthogonal from its [[Construct]].

But I do not do it by "injecting" [[Call]] into plain class object - I 
don't see the need for such operations. Simply take a function (it has 
its [[Call]] defined well), and make it a `class` - give it consisent 
.prototype and [[Construct]].

The same thing that was done to plain object in `class` construct.

> The main message of this proposal is that beyond fixing super/new
> inconsistency, it opens new world for "classes", not restricted by
> the legacy tightly bound class===constructor objects.
>
> And by making class to return this kind of 'decoupled' classes, this
> widened view to "who may be used as a class in new / extends?" is
> effectively blessed.
>
> You cannot open it later. Because there will be lagacy code that
> already uses `class`.
>
> (I'd stress again, that, imo, that change is very little, too little
> for the fruits it can bear. Fix me if it breaks something critical)

In the subsequent reply t9 myself, I sketched how this "I want to 
specify what object would represent my class" could have been fused into 
language itself:

> [The makeClass API] can be in some version of `static` syntax in the
> future. But it allows richer outcomes:
>
> Not mere:
>   class Foo {
>     ...
>     static {
>       // must be some predefined kind of body
>     }
>   }
>
> but instead:
>   claas Foo {
>     ...
>     static Expr;
>   }
>
> where Expr can be any object that would be [[Construct]]ified and
> bound to Foo. So it _can_ be {...} object literal, but it can be
> anything else, existing object that user want to class-ify, a
> function, ...

With this, I can do:

   class Foo {
     constructor (...args) { /* initialization code */ }
     ...
     static ...args => { /* invocation code */ };
   }

But it is nothing special, just embedded API from below (or, in other 
PoV, specifying what object to use in `class` to [[Construct]]ify and 
return; when not specified, plain object is used).

And once more, this is second step; the first is the separation itself.

> Allen

Thanks, Herby

P.S.: The new/super issue can be solved, as well as free default 
constructor achieved, even without the separation; the critical for the 
fix and the free default constructor is the new [[Construct]] semantics 
calling .prototype.constructor.
The idea of separation was just the continuation of ideas after this new 
[[Construct]] behaviour - when F is not [[Call]]ed anymore, why to keep 
the binding?


More information about the es-discuss mailing list