getter and setter inheritance

# Kris Zyp (17 years ago)

A question of how inheritance/delegation should work with getters and setters was raised in a recent discussion. If there is an object A whose prototype is object B, and object A defines a getter for property foo, and object B defines a setter for property foo, and we write a value to A.foo, should the setter defined on object B be called? If A didn't define any property on A, the setter would be inherited from B and would be called when A.foo was modified. However, with the getter defined on A, should the inheritance stop at A, because there is a slot defined, or should it continue along the prototype chain because there was no setter defined for that property (nor plain value)? The question also applies when the getter and setter are reversed. When a property is accessed and there is a setter defined, but no getter, should we continue to down the prototype chain to find a getter or a plain value, or stop and return undefined? AFAICT, ES4 doesn't explicitly define which is the correct behavior. Firefox follows the former behavior:

B={set foo(v){foo = v}, get bar(){return "bar value"}} A={get foo(){return "foo value"}, set bar(v){bar = v}, proto:B}

A.bar -> undefined

A.foo = 'new value' -> setter is not called, foo is not set

Thanks, Kris

# Lars Hansen (17 years ago)

Very timely questions. I've been worrying about similar things this week (more in the context of the ES4 type system) and not yet reached any conclusions.

My view is that getters and setters introduce properties and that whatever we do should be appropriate to that model.

Others seem to think that getters and setters introduce a way of invoking normal methods by a different syntax and that they are truly just methods. In the world of classes and instances that may be appropriate, but not in the object-and-property world of ES3, I expect.

# liorean (17 years ago)

2008/5/9 Lars Hansen <lhansen at adobe.com>:

My view is that getters and setters introduce properties and that whatever we do should be appropriate to that model.

That is pretty much my view as well. Just a couple of questions that might need addressing, if it hasn't been dealt with already:

  • Can you have a property together with a getter and/or a setter?
  • If so:
    • Is there any way to reach that property from inside the getter/setter without ending up recurring into the getter/setter?
    • Can a setter modify the property if there is a property but no getter?
    • Can a getter read the property if there is a property but no setter?

Others seem to think that getters and setters introduce a way of invoking normal methods by a different syntax and that they are truly just methods. In the world of classes and instances that may be appropriate, but not in the object-and-property world of ES3, I expect.

Or one could consider an ES3 property as a getter/setter pair, if one wished.

# Lars Hansen (17 years ago)

(One bike ride and one cup of coffee later.)

Clearly there is a difference between class/interface inheritance on the one hand and prototype inheritance on the other.

In either case I think the introduction of a setter and/or a getter in a context introduces definitions for both in that context, essentially a special property that holds a getter/setter pair. A missing getter/setter is generated (that's what ES4 specifies now.) That means that in prototype contexts, if an object has a getter or a setter for a field, the prototype will never be searched for the missing half. In a class context, getters and setters can be overridden because the class instance only has the one special property with the getter/setter pair, and the values in that property depend on the class that the instance is an instance of. So different classes have different pairs.

There's no particular problem with interfaces holding getter and setter declarations, they are just constraints on the contents of the implementing class -- the class must have a matching getter or setter definition (possibly inherited from base class). That said, getters and setters in interfaces feels pretty lame, because what they're doing is basically requiring a particular implementation of a property. A "good" interface would require there to be a property "x" with a particular type, but not worry about whether it was introduced by "var", "let", "const", or implemented as a getter/setter. So, using "var" to stand for all of those possibilities:

interface ArrayLike.<T> { var length: double; meta function get(n):T; meta function set(n,v:T); }

In practice most types implementing ArrayLike would use a getter/setter pair for the length field, but why should the type worry about that?

# Lars Hansen (17 years ago)

-----Original Message----- From: es4-discuss-bounces at mozilla.org [mailto:es4-discuss-bounces at mozilla.org] On Behalf Of liorean Sent: 9. mai 2008 09:34 To: es4-discuss at mozilla.org Subject: Re: getter and setter inheritance

2008/5/9 Lars Hansen <lhansen at adobe.com>:

My view is that getters and setters introduce properties and that whatever we do should be appropriate to that model.

That is pretty much my view as well.

See my more recent message for an elaboration of that, but I don't think we disagree.

Just a couple of questions that might need addressing, if it hasn't been dealt with already:

  • Can you have a property together with a getter and/or a setter?

Not in ES4, though you can use different namespaces of course:

class C { function get x() private::x function set x(v) private::x = toInt32(v) private var x = 0 }

  • If so:
    • Is there any way to reach that property from inside the getter/setter without ending up recurring into the getter/setter?
    • Can a setter modify the property if there is a property but no getter?
    • Can a getter read the property if there is a property but no setter?

The catchall mechanism has to deal with these issues and have a way of asking for default behavior to be invoked. I'll have more to say about this next week.

# Mark S. Miller (17 years ago)

2008/5/9 Kris Zyp <kris at sitepen.com>:

A question of how inheritance/delegation should work with getters and setters was raised in a recent discussion.

Hi Kris,

The way I've been thinking of getters and setters is as if there are two kinds of properties:

  • A data property is defined by its current value, and whether the value is writable. If the value is not writable, the property is considered read-only.

  • A procedural property is defined by a getter function and an optional setter function. If there is no setter function, the property is considered read-only.

For both kinds of properties, a property is further defined by its property name, whether the property is enumerable, and whether the property's definition is redefinable. (Where redefinable implies deletable.) This would be reflected (so to speak) concretely in the results of Object.getProperties and Object.getProperty, and in the argument of Object.defineProperties.

Object.getProperty(x, 'foo') => {value: 3, writable: false,

enumerable: false, redefinable: false} Object.getProperty(x, 'bar') => {getter: function(){return 3;},

enumerable: false, redefinable: false}

in which case x.foo and x.bar are behaviorally identical for non-reflective clients.

If there is an object A whose prototype is object B, and object A defines a getter for property foo, and object B defines a setter for property foo, and we write a value to A.foo, should the setter defined on object B be called?

I think it should be illegal to define a setter without defining a getter. A procedural property should either have a getter or have a getter and a setter.

Assuming B defines a getter and a setter for foo, and A defines a getter for foo, the answer should be no. A's own foo is a read-only procedural property that fully masks B's foo.

If A didn't define any property on A, the setter would be inherited from B and would be called when A.foo was modified.

Yes.

However, with the getter defined on A, should the inheritance stop at A, because there is a slot defined, or should it continue along the prototype chain because there was no setter defined for that property (nor plain value)?

Stop at A. s/slot/property

The question also applies when the getter and setter are reversed. When a property is accessed and there is a setter defined, but no getter,

That case should be rejected as illegal.

should we continue to down the prototype chain to find a getter or a plain value, or stop and return undefined? AFAICT, ES4 doesn't explicitly define which is the correct behavior. Firefox follows the former behavior:

B={set foo(v){foo = v}, get bar(){return "bar value"}}

B.foo should be rejected as illegal.

A={get foo(){return "foo value"}, set bar(v){bar = v}, proto:B}

A.bar should be rejected as illegal.

# Mark S. Miller (17 years ago)

On Fri, May 9, 2008 at 9:49 AM, Lars Hansen <lhansen at adobe.com> wrote:

From: es4-discuss-bounces at mozilla.org [mailto:es4-discuss-bounces at mozilla.org] On Behalf Of liorean 2008/5/9 Lars Hansen <lhansen at adobe.com>:

My view is that getters and setters introduce properties and that whatever we do should be appropriate to that model.

That is pretty much my view as well.

See my more recent message for an elaboration of that, but I don't think we disagree.

It seems we have all around agreement, at least on this aspect!

Just a couple of questions that might need addressing, if it hasn't been dealt with already:

  • Can you have a property together with a getter and/or a setter?

Not in ES4, though you can use different namespaces of course:

And not in my thinking about ES3.1 properties either. At any moment in time, a property is either a data property or a procedural property. (If it's not redefinable, then it is forever either a data property or a procedural property.)

# Kris Zyp (17 years ago)

When an object A is defined that inherits from it's prototype object B that has a getter and setter defined for "foo", and object A wants to inherit the behavior of B except for a slight modification to the getter behavior, it could be advantageous if the setter could still be inherited without having to redefine it on object A. If the developer wants to make property "foo" read-only, this can still easily be done by creating a setter that throws an exception (or does nothing). That being said, I would still favor the approach of treating getters/setters as a full slot, where inheritance doesn't go past half-definitions because that is behavior that is currently used on the web today.

Also, the idea of making setters without getters illegal is interesting. However, while they may be rare, I think setters without getters has use cases. This seems like unnecessary arbitrary restriction to dissallow setters without getters. Furthermore, this is not the behavior of current browsers, there is no precedent in JavaScript implementations for preventing this combination. Is there other reasons for this restriction that I am not aware of?

Thanks, Kris

----- Original Message ---

# Mark S. Miller (17 years ago)

On Sun, May 11, 2008 at 4:40 PM, Kris Zyp <kris at sitepen.com> wrote:

[...] That being said, I would still favor the approach of treating getters/setters as a full slot, where inheritance doesn't go past half-definitions because that is behavior that is currently used on the web today.

Great!

Also, the idea of making setters without getters illegal is interesting. However, while they may be rare, I think setters without getters has use cases. This seems like unnecessary arbitrary restriction to dissallow setters without getters. Furthermore, this is not the behavior of current browsers, there is no precedent in JavaScript implementations for preventing this combination. Is there other reasons for this restriction that I am not aware of?

Not really. I can live with write-only procedural properties. I don't think it changes any of the other points.

When reading a write-only property, i.e., a procedural property with a setter and no getter, one reads undefined, even in strict mode. Right? So an absent getter is always equivalent to a getter of "function(){return undefined;}"?

# P T Withington (17 years ago)

On 2008-05-09, at 12:46 EDT, Lars Hansen wrote:

(One bike ride and one cup of coffee later.)

Clearly there is a difference between class/interface inheritance on
the one hand and prototype inheritance on the other.

In either case I think the introduction of a setter and/or a getter
in a context introduces definitions for both in that context, essentially a special property that holds a getter/setter pair. A missing getter/setter is generated (that's what ES4 specifies now.) That
means that in prototype contexts, if an object has a getter or a setter
for a field, the prototype will never be searched for the missing half.
In a class context, getters and setters can be overridden because the class instance only has the one special property with the getter/setter
pair, and the values in that property depend on the class that the
instance is an instance of. So different classes have different pairs.

(I've only been to spin class, but I've had 1 coffee and 2 teas.)

When "A missing getter/setter is generated", what is its
functionality? Does it just error?

Can I call a super getter/setter method (I hope)? What is the syntax
for that?

# Lars Hansen (17 years ago)

The generated getter and setter ought to have roughly the same behavior as the getting and setting behavior for a property on the class.

The behavior for reading and writing properties is this, IIRC:

If the class is compiled in standard mode, then reads from nonexistent properties return undefined. Writes to nonexistent properties on non-dynamic classes fail silently, whereas writes to nonexistent properties on dynamic classes create those properties.

If the class is compiled in strict mode, then reads from nonexistent properties on non-dynamic classes throw an exception (no such property); reads from nonexistent properties on dynamic classes return undefined. Writes to nonexistent properties on non-dynamic classes throw an exception (no such property), whereas writes to nonexistent properties on dynamic classes create those properties.

For generated getters and setters (in classes at least), it seems that matching that behavior, except for property creation, is roughly right. I'd say that the write ought to be silently ignored on non-strict classes and that an error should be thrown on strict classes.

And yes, it ought to be possible to invoke the superclass getter, the syntax is as for accessing a property on a superclass, "super.p" for the immediate base class or "super(C).p" for classes higher up the inheritance chain.

# Erik Arvidsson (17 years ago)

How does super lookups and calls work for getters and setters in ES3.1 and ES4? It seems like the following scheme should work.

Given the following code in ES4:

class A { function get x() { ... } function set x(value) { ... } }

class B extends A { private let _x = 0; function get x() { return super.x; } function set x(value) { _x = value; super.x = value; } }

In JS1.7 I think the following works

function A() { ... } A.prototype.defineGetter('x', ...); A.prototype.defineSetter('x', ...);

function B() { A.call(this); } B.prototype = new A; // Or use helper to prevent calling A's constructor while setting up the prototype object

B.prototype._x = 0;

B.prototype.defineGetter('x', function() { var aGetter = A.prototype.lookupGetter('x); return aGetter.call(this); };

B.prototype.defineSetter('x', function(value) { this.x_ = value; var aSetter = A.prototype.lookupSetter('x); aSetter.call(this, value); };

Would ES3.1 work by simply replacing lookup(G|S)etter by Object.getProperty?

# Mark S. Miller (17 years ago)

On Mon, May 12, 2008 at 10:10 AM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

Would ES3.1 work by simply replacing lookup(G|S)etter by Object.getProperty?

Yes, I think so.

B.prototype.defineGetter('x', function() { var aGetter = A.prototype.lookupGetter('x); ...

Object.defineProperties(B.prototype, {x: { getter: function() { var aGetter = Object.getProperty(A.prototype, 'x').getter; ...