The class operator: a bridge between object and function exemplers

Mark S. Miller erights at google.com
Mon Nov 14 17:13:02 PST 2011


On Mon, Nov 14, 2011 at 2:32 PM, Brendan Eich <brendan at mozilla.com> wrote:

> I'm really warming up to this radical class-as-unary-get-the-constructor
> operator.
>

Likewise.


>
> But, I think the missing 'constructor' method bug will bite people. Should
> class throw if there is no own 'constructor' property? Mutating is out, and
> copying is out too.
>

But, when we're testing a value rather than constructing a class, I think
we're getting it wrong to think of "class x" as being sugar, or even
approximately sugar, for "x.constructor". If x is simply an instance, it
may well have an own property named "constructor" that has nothing to do
with the class-like pattern. Better would be for it to be (approximately)
sugar for "Object.getPrototypeOf(x).constructor" (or more accurately, in
spec language, "x.[[Prototype]].constructor". But this isn't really right
either. I think the meaning of "class x" in this proposal should produce
the following invariant:

    x instanceof class x

This suggests the following executable spec, which is only reasonable if it
can be optimized to almost nothing in the typical case:

    function [[GetClass]](x) {
      while (true) {
        const proto = Object.getPrototypeOf(x);
        if (proto === null) { return null; /* or void 0? */ }
        let result;
        if (hasOwnProperty(proto, 'constructor') &&
            (result = proto.constructor) &&
            typeof result === 'function' &&
            result.prototype === proto) {
          return result;
        }
      }
    }

This does not guarantee no false positives, but since we're just trying to
(duck) recognize a pattern, we can't. But the above definition will avoid
almost all false positives, and in does guarantee the instanceof
implication.

Many JS programmers (including myself) do not initialize, for example, a
Point.prototype to be a valid (prototypical) point. And I think we are
generally right not to do so. Thus, the fact that "!(Point.prototype
instanceof Point)" in JavaScript is a virtue, not a flaw of the language.
We treat Point.prototype effectively as a form of vtable, providing just
the things that all points share, not the things that should be initialized
per point. Since, for us, Point.prototype is not a kind of Point, it would
be a terrible mistake if "class Point.prototype === Point".

Much existing code that engages in prototypal encoding on the class pattern
remembers to hook up the constructor. And much forgets. Given an instance x
which effectively inherits from a legacy Foo "class", which forgot to hook
up constructor, which "subclasses" a Bar "class" which does, there is no
way to recover the Foo constructor function from x. The best one can do is
recover the Bar constructor function, which the spec above does.

Perhaps this hit-or-miss nature of "constructor" can become a virtue. Given
the need that Allen separately mentioned for a species-like notion,
paralleling "species" in Smalltalk, "new class this(...)" would make a new
instance of the same species as this.

Given this meaning of "class" when testing a value, I'm not sure if we can
rescue the pun above by which we use the same operator to define a class.
If forced to choose only one of these two roles for the "class" keyword,
I'd prefer to use it only to define a class, not to test values. But having
only just noticed this discrepancy, I am also not sure we cannot rescue the
pun. Let's try.

-- 
    Cheers,
    --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20111114/2c31179d/attachment.html>


More information about the es-discuss mailing list