New: proposal-class-modifiers
How would this interop with Object.create
, Reflect.construct
, and
Object.setPrototypeOf
?
Object.create and Object.setPrototypeOf operate on a different level than
what I'm targeting. I can conceive of a means to make even these functions
respect these tokens. However, if I were going to do that, I'd want these
tokens to be applicable to objects and functions directly.
Reflect.construct is essentially part of the process for new
and would be
required to respect these tokens. I'm still open on how far the effect of
these tokens should extend.
For abstract
, I could see that being three things:
- Throw a reference error if an abstract method is called with no concrete implementation
- Throw a type error if the constructor is called without its abstract methods implemented
- Throw a type error if a subclass fails to implement all remaining abstract methods and is not itself abstract
Each of these could be reasonably tacked on at the end.
For final
, you'll need to create a way to block all means of
Object.create
, with class constructors being the sole exception. That
complicates the mechanism tremendously, since prototype-based inheritance
alone can't enforce this unless you make Object.setPrototypeOf(obj, parent)
no longer directly equivalent to obj.[[Prototype]] = parent
.
Wouldn't it be better abstract
's target.prototype === newTarget.prototype
check to be target === newTarget
? It's not really
the prototype that is at issue here, it is the class constructor itself. It
would not be good if you could sidestep abstract
by taking the prototype
from the class and making a new constructor for it.
For final
is it worth throwing an extends
time vs just throwing when
the class is instantiated? It seems like final
would otherwise require
new private state on the constructor itself, where otherwise it would
essentially just be the negation of the check abstract
was doing already.
Also, if those make sense, what do we gain by having these be keywords instead of decorators?
@Logan If this helps explain my stance, I'm skeptical of the use, and it really disrupts the consistency of JS's object model quite a bit. It makes sense to enforce at a static level, but not really at a dynamic runtime level. Also, this is lacking precedent in other dynamic languages with classes: Python, Ruby, Smalltalk, Lua, Io, and pretty much every other dynamic language with class- or prototype-based inheritance (that wasn't made for the JVM) doesn't support final classes. Python (IIRC through a decorator) and Ruby support abstract methods, but not final classes/methods.
Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com
@Logan
It would not be good if you could sidestep
abstract
by taking the
prototype from the class and making a new constructor for it.
That's precisely why it's a prototype test and not a constructor test. Everything that defines the initial "shape" of the class is on the prototype. The constructor only comes into play after a new object has been created with the corresponding prototype attached. If the prototype of the constructor being called matches the prototype of newTarget, and that prototype has been flagged as abstract, the new object creation is halted immediately. Constructor comparisons can easily be spoofed simply by Proxying the constructor. Where target would be the un-Proxied constructor, newTarget would be the Proxy. They wouldn't match so the abstract test would fail and an object would be constructed.
Contrary to what you may have been led to believe, the definition of a class is its prototype. There are those who refuse to accept this reality, and that's fine. But if you make the wrong assumptions as a result of an incorrect understanding, the results won't turn out how you want.
With final
, the point is that a class
that extends from a final class
cannot be defined, not simply "cannot be instantiated". Even without
instantiating, a defined class
can still be used.
...it would essentially just be the negation of the check
abstract
was
doing already.
That's precisely correct. These 2 new tokens are the polar opposites of
each other. One restricts construction to cases where the prototypes of
target
and newTarget
match (final
), while the other requires that
they don't match (abstract
). That's also the reason they cannot be used
together.
@Isiah
Sounds like part of what you're looking for is abstract methods rather than
an abstract class
. I wasn't targeting that. If I were, that would be
something to add to proposal-common-class-modifiers. If I were to look at
what you're describing more holistically, you're looking for "interface", a
way to set up a contract for what's supposed to be there and require it all
to be present. I wasn't targeting that either. In fact, that would probably
be a good proposal all by itself. I'll give an implementation for that some
thought. However, with this proposal, all I'm thinking about is inheritance
constraints.
As for final
, I need you to convince me that it's worth while preventing
Object.create and Object.setPrototype. It's shouldn't be that difficult to
do, but I don't have an understanding of why you'd want to. Especially when
you consider that when some form of private goes in (assuming it's not
Symbol.Private) class instances will be quite a bit different than vanilla
objects.
I get that final
is something not seen in dynamic languages (outside the
JVM), but it is a useful tool in giving developers control over how their
code can be used. Isn't that part of the point of trying to implement
private data in ES?
Contrary to what you may have been led to believe, the definition of a
class is its prototype. There are those who refuse to accept this reality, and that's fine. But if you make the wrong assumptions as a result of an incorrect understanding, the results won't turn out how you want.
Can you please keep this extremely condescending tone out of these discussions? I don't know how you expect people to get on board with your proposals when you constantly imply that those that disagree with you don't know what they are talking about.
The constructor only comes into play after a new object has been created
with the corresponding prototype attached.
A prototype with an entirely different constructor is an entirely different class. The behavior of a constructor is critical to the behavior of an instance of a class, and in an ideal world is responsible for defining all of the own properties of the instance. I don't understand how a prototype on its own could be considered to represent the class as a whole.
Constructor comparisons can easily be spoofed simply by Proxying the
constructor.
I'm not sure I see how that would be any different for intercepting the
.prototype
access.
With
final
, the point is that aclass
that extends from a `final
class***cannot be defined***, not simply "cannot be instantiated". Even without instantiating, a defined
class` can still be used.
I don't disagree that it it would throw as early as possible in an ideal
world, just that it seems to complicate the implementation dramatically
with out a ton of benefit that I can see. I think it does depend on your
overall goal though. Is final
meant to mean that the prototype may never
exist anywhere but the head of the prototype chain, or just that a subclass
may not be constructed? That I guess goes to Isiah's questions about
Object.create
and such as well. It seems like 99% of the time I'd be much
more interested in preventing creation of subclasses. I don't particularly
care about the exact location of an object in the prototype chain.
Can you please keep this extremely condescending tone out of these
discussions? I don't know how you expect people to get on board with your proposals when you constantly imply that those that disagree with you don't know what they are talking about.
My apologies if you thought my tone condescending. My intent was merely to
point out that assuming the constructor to be the correct point of focus
for the implementation of abstract
will only lead to implementations that
don't work as desired. I tend to speak (and write) in a very matter-of-fact
tone for things that I have tested and proven to myself repeatedly. Again,
it wasn't my intention to do anything more than state the facts and explain
the dangers of potential misconceptions.
A prototype with an entirely different constructor is an entirely
different class. The behavior of a constructor is critical to the behavior of an instance of a class, and in an ideal world is responsible for defining all of the own properties of the instance. I don't understand how a prototype on its own could be considered to represent the class as a whole.
I get what you're saying, and on that point, you're not wrong, especially for base classes. However, consider the inheritance case. The instance object, before any own properties can be attached, has the prototype of the class and its base classes attached. Even if the constructors are empty, the class is still complete. After construction of the instance object, the job of the constructor can just as easily be performed by any other function of the class. The constructor itself is simply a convenient place to perform this initialization before any other code has the opportunity to touch the instance. I don't mean to argue this point. I've already accepted that my perspective on this is not the popular understanding. However, every attempt I've made to hash out how things work (for several proposals) using a constructor-first perspective led to unacceptable trade-offs and complications.
I'm not sure I see how that would be any different for intercepting the
.prototype
access.
Whether it's the constructor or prototype, a private slot would need to be
used to mark the class as abstract. Since this proposal (currently) only
affects class
definitions, and class
definitions mark the prototype
as non-writable, non-configurable, there's no way for the original
constructor to have its prototype replaced. Even Proxy can't change this.
If the flag is put on the constructor, then it is easily bypassed by
manually creating a constructor function and copying the prototype object.
However, if the flag is placed on the prototype object itself, there is no
way to spoof it. This also makes it possible for final
to prevent
Object.create
and Object.setPrototypeOf
, but I'm still waiting for
Isiah to convince me it's a good thing to do.
Is
final
meant to mean that the prototype may never exist anywhere but
the head of the prototype chain, or just that a subclass may not be constructed?
It's closer to the former than the latter. At present, I'm thinking this
should only affect classes, but if Isiah (or anyone else) comes up with a
good reason why it should also block Object.create
and
Object.setPrototypeOf
, then it would be completely the former. Just
preventing construction of a subclass instance doesn't seem to have much
merit to me.
This proposal is intended to add
abstract
andfinal
toclass
definitions so as to modify the inheritability of a class.rdking/proposal