extends null

# Axel Rauschmayer (5 years ago)

If I’m reading the latest spec draft correctly then

class C extends null {
}

produces the following result:

  1. Constructor kind: derived
  2. Prototype of C: Function.prototype
  3. Prototype of C.prototype: null

Neither #2 nor #3 seems very useful:

  • #2 means that a super-constructor call is allowed but throws an exception, because Function.prototype is not constructible. The default constructor will perform a super-constructor call. As a result, you are forced to explicitly return an object from the constructor if you don’t want an exception to be thrown.

  • #3 means that the constructor doesn’t even create objects whose prototype is null, but objects whose prototype is an object whose prototype is null.

Therefore my question: is this useful for anything? Can’t extends null be turned into something useful? Even treating extends null as equivalent to a missing extends clause seems preferable.

# Mark S. Miller (5 years ago)

No, the problem is that

class C extends X {

when X turns out to be null must be an error along exactly these lines. Everywhere else in the language where X is accepted as an expression evaluated to a value, if X evaluates to null, this is the same thing as placing null in that position. Consider:

const NULL = null; // C nostalgia

class C extends NULL {

It would be awful for this to be different than

class C extends null {

If you really want to write a class whose prototype.__proto__ is null, imperatively make it so after the class declaration. We don't need to compromise the consistency of the language to make this rare case less ugly.

# Axel Rauschmayer (5 years ago)

But it’s not an error! Either of the following two classes fail later, when you instantiate them, but not right away.

const X = null;
class C extends X {}

class D extends null {}

I’m arguing that both produce weird constructors. I’d much prefer an early error. Or dynamically switching from "derived" to "base" (but you seem to be saying that doing this dynamically is not a good idea).

# Mark S. Miller (5 years ago)

On Sat, Feb 14, 2015 at 1:21 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

But it’s not an error! Either of the following two classes fail later, when you instantiate them, but not right away.

const X = null;
class C extends X {}

class D extends null {}

I didn't mean to imply an early error. The "extends X" can only produce a dynamic error, so I'm arguing that "extends null" should do the same.

I’m arguing that both produce weird constructors. I’d much prefer an early error.

You can't know early that X is null.

Or dynamically switching from "derived" to "base" (but you seem to be saying that doing this dynamically is not a good idea).

Dynamically switching from derived to base does not work, because our super semantics depends statically on the difference.

# Axel Rauschmayer (5 years ago)

On 14 Feb 2015, at 22:26, Mark S. Miller <erights at google.com> wrote:

I didn't mean to imply an early error. The "extends X" can only produce a dynamic error, so I'm arguing that "extends null" should do the same.

When I say “early”, I don’t mean “static”, I mean: dynamically, when the class definition is evaluated, not later when the class is instantiated via new.

I’m not seeing a dynamic error in the spec when the extends clause is null (step 6e): people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation, people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation

Or dynamically switching from "derived" to "base" (but you seem to be saying that doing this dynamically is not a good idea).

Dynamically switching from derived to base does not work, because our super semantics depends statically on the difference.

Ah, checked statically, I wasn’t aware! Hadn’t found the check beforehand. Searched some more and it is indeed there, in 14.5.1.

# Mark S. Miller (5 years ago)

On Sat, Feb 14, 2015 at 1:45 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

I’m not seeing a dynamic error in the spec when the extends clause is null (step 6e): people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation

That's a good suggestion. AFAICT, it would be an improvement. I don't see any downside.

Allen?

# Erik Arvidsson (5 years ago)

Making it a dynamic error at class definition time to extend null would work but the motivation for not doing that was that someone might want to create a class that has a {__proto__: null} prototype. Personally, I would be fine with saying that this case is so rare that it would be better to have that dynamic error at class definition time.

# Axel Rauschmayer (5 years ago)

Interesting. I have never seen this pattern and don’t see what it could be good for. Thus, a dynamic error at class definition time sounds good to me.

# Kevin Smith (5 years ago)

Interesting. I have never seen this pattern and don’t see what it could be good for. Thus, a dynamic error at class definition time sounds good to me.

The purpose would be defining a class whose instances don't have Object.prototype on their prototype chain. If "extends null" doesn't work, then I think you'd have to do something like this to achieve the same?

function NullBase() {}
NullBase.prototype = Object.create(null);

class C extends NullBase {}
# Mark S. Miller (5 years ago)

That still wouldn't work at runtime because of the super semantics of C as a derived class. Instead

class C {....}

C.prototype.__proto__ = null;

Yes, it is ugly, but it is an odd case, so still obeys Kay's dictum:

"Simple things should be simple. Complex things should be possible" --Alan Kay

# Jordan Harband (5 years ago)

Rather than making "extends null" alone a runtime error at class evaluation time, is there a reason not to instead, make only a reference to "super" in the constructor of a class that extends null be a runtime error at class evaluation time?

# Marius Gundersen (5 years ago)

Can't this be solved by returning a null object from the constructor?

class Null{
  constructor() {
    return Object.create(null);
}
}

class MyClass extends Null{

}
let foo = new MyClass();
foo.toString() //ReferenceError
# Erik Arvidsson (5 years ago)

No that would not work either. You want an object that has its [[Prototype]] set to MyClass.prototype.

# Isiah Meadows (5 years ago)

Couldn't one of these be enough?

var Null = {prototype: {__proto__: null}};
var Null = {prototype: Object.create(null)};

class Foo extends Null {}
# Axel Rauschmayer (4 years ago)

Is this the best way to use extends null?

class C extends null {
    constructor() {
        let _this = Object.create(C.prototype);
        return _this;
    }
}

You can’t use this, because it can’t be initialized via a super-constructor call: the super-constructor is Function.prototype (which can’t be constructor-called).

# Glen Huang (4 years ago)

Isn't super-constructor null in this case?

From step 4 in people.mozilla.org/~jorendorff/es6-draft.html#sec-getsuperconstructor, people.mozilla.org/~jorendorff/es6-draft.html#sec-getsuperconstructor

superConstructor is C.[GetPrototypeOf]

which should be null after the class definition, if I'm not wrong. (But it finally throws due to type error, so technically speaking, you can't even reference the super constructor)

I can't think of a better way of extending null, but I wonder what's the use case? null usually represents lacking of a value, and extending the lacking of a value? My brain hurts.

# Claude Pache (4 years ago)

Le 7 mai 2015 à 10:25, Axel Rauschmayer <axel at rauschma.de> a écrit :

Is this the best way to use extends null?

class C extends null {
    constructor() {
        let _this = Object.create(C.prototype);
        return _this;
    }
}

No, you should say: Object.create(new.target.prototype)

# Claude Pache (4 years ago)

Le 7 mai 2015 à 11:49, Glen Huang <curvedmark at gmail.com> a écrit :

Isn't super-constructor null in this case?

From step 4 in people.mozilla.org/~jorendorff/es6-draft.html#sec-getsuperconstructor, people.mozilla.org/~jorendorff/es6-draft.html#sec-getsuperconstructor

superConstructor is C.[GetPrototypeOf]

which should be null after the class definition, if I'm not wrong. (But it finally throws due to type error, so technically speaking, you can't even reference the super constructor)

No, as a special case of the extends semantics, C.[[GetPrototypeOf]]() will be %FunctionPrototype%; see step 6.e.ii of: people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation

The reason is presumably that, since the constructor is a function, it should always have the methods for functions (.bind, .call, etc.) on its prototype chain.

The true meaning of C extends null is the following: The instances of C won’t have %ObjectPrototype% in their prototype chain. (For the use cases, don’t ask me.)

# Glen Huang (4 years ago)

Ah, didn't notice the special case, thanks for the heads up.

Having methods for function in the prototype chain makes sense.

Another related question: If I want to override a superclass's constructor(), without calling it, I should do something like this?

class A extends B {
   constructor() {
       let _this = Object.create(new.target.prototype);
       return _this;
   }
}

Anyway to use this here? Having to use a different name is a bit painful.