ES accessor usage guidelines (Was: Map/Set.prototype.size)

# Allen Wirfs-Brock (13 years ago)

On Oct 12, 2012, at 2:16 PM, David Herman wrote:

On Oct 12, 2012, at 12:14 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

On Fri, Oct 12, 2012 at 11:16 AM, David Bruant <bruant.d at gmail.com> wrote:

Firefox has implement a Map/Set.prototype.size method to query the number of mapping/elements. It's not in the strawman. It appears in the latest draft, though weirdly enough Map.prototype.size is a function with an unused argument. What about making it an getter instead of a function? I guess we also don't need the extra parens to request for Map#keys, Map#values, Map#items.

For keys, values and items I think they are cleaner as methods since they return a new iterator every time.

Agreed.

Dave

I buy making Map/Set size an accessor. This will be the first such property in the ES specification and it would be good to have some guidelines to help us make consistent decisions about using accessors in the future.

Erik and Tab floated a couple reasons why items/keys/values should not be accessors.

Does anybody want to take a crack at writing up a complete set of design rules for when a built-in property should/shouldn't be defined as an accessor?

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

On Oct 12, 2012, at 2:16 PM, David Herman wrote:

On Oct 12, 2012, at 12:14 PM, Erik Arvidsson<erik.arvidsson at gmail.com> wrote:

On Fri, Oct 12, 2012 at 11:16 AM, David Bruant<bruant.d at gmail.com> wrote:

Firefox has implement a Map/Set.prototype.size method to query the number of mapping/elements. It's not in the strawman. It appears in the latest draft, though weirdly enough Map.prototype.size is a function with an unused argument. What about making it an getter instead of a function? I guess we also don't need the extra parens to request for Map#keys, Map#values, Map#items. For keys, values and items I think they are cleaner as methods since they return a new iterator every time. Agreed.

Dave

I buy making Map/Set size an accessor. This will be the first such property in the ES specification and it would be good to have some guidelines to help us make consistent decisions about using accessors in the future.

Erik and Tab floated a couple reasons why items/keys/values should not be accessors.

Does anybody want to take a crack at writing up a complete set of design rules for when a built-in property should/shouldn't be defined as an accessor?

Simpler is better. Tab, Arv, and others hit the big one. Here's a first cut:

  • get-only accessor for non-writable property must be idempotent, effect-free.

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, any children that become garbage, e.g. when trimming .length on an array-like).

  • get/set accessors should reflect as such. This means Array.length gets a legacy dispensation, and proto needs poisoning.

  • anything else is a method.

# Yehuda Katz (13 years ago)

Yehuda Katz (ph) 718.877.1325

On Mon, Oct 15, 2012 at 12:23 PM, Brendan Eich <brendan at mozilla.org> wrote:

Allen Wirfs-Brock wrote:

On Oct 12, 2012, at 2:16 PM, David Herman wrote:

On Oct 12, 2012, at 12:14 PM, Erik Arvidsson<erik.arvidsson@**gmail.com<erik.arvidsson at gmail.com>>

wrote:

On Fri, Oct 12, 2012 at 11:16 AM, David Bruant<bruant.d at gmail.com>

wrote:

Firefox has implement a Map/Set.prototype.size method to query the number of mapping/elements. It's not in the strawman. It appears in the latest draft, though weirdly enough Map.prototype.size is a function with an unused argument. What about making it an getter instead of a function? I guess we also don't need the extra parens to request for Map#keys, Map#values, Map#items.

For keys, values and items I think they are cleaner as methods since they return a new iterator every time.

Agreed.

Dave

I buy making Map/Set size an accessor. This will be the first such property in the ES specification and it would be good to have some guidelines to help us make consistent decisions about using accessors in the future.

Erik and Tab floated a couple reasons why items/keys/values should not be accessors.

Does anybody want to take a crack at writing up a complete set of design rules for when a built-in property should/shouldn't be defined as an accessor?

Simpler is better. Tab, Arv, and others hit the big one. Here's a first cut:

  • get-only accessor for non-writable property must be idempotent, effect-free.

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, any children that become garbage, e.g. when trimming .length on an array-like).

DOM methods like innerHTML= seem to violate this particular wording (but perhaps not the spirit?)

# David Bruant (13 years ago)

2012/10/15 Brendan Eich <brendan at mozilla.org>

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably,

I think that "unobservably" will be very hard (if not impossible in most cases) to achieve with proxies. "Unobservable except to the receiver handler if the receiver is a proxy" sounds like a more achievable constraint.

any children that become garbage, e.g. when trimming .length on an array-like).

I'm not sure I understand this part. Did you finish your sentence? What does "children" mean here?

Otherwise, I agree with the rest.

# Allen Wirfs-Brock (13 years ago)

On Oct 15, 2012, at 10:18 AM, David Bruant wrote:

2012/10/15 Brendan Eich <brendan at mozilla.org>

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, I think that "unobservably" will be very hard (if not impossible in most cases) to achieve with proxies. "Unobservable except to the receiver handler if the receiver is a proxy" sounds like a more achievable constraint.

any children that become garbage, e.g. when trimming .length on an array-like). I'm not sure I understand this part. Did you finish your sentence? What does "children" mean here?

I think something like "may only have an observable effect on the receiver object" is closer to what we want. The abstraction represented by an object may well use other objects as part as its internal implementations and a setter might change the state of such implementation objects. However, the implementation should not expose any such secondary objects in manner that such effect could be observed by outsiders.

# Mark S. Miller (13 years ago)

On Mon, Oct 15, 2012 at 9:17 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Oct 12, 2012, at 2:16 PM, David Herman wrote:

On Oct 12, 2012, at 12:14 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

On Fri, Oct 12, 2012 at 11:16 AM, David Bruant <bruant.d at gmail.com> wrote:

Firefox has implement a Map/Set.prototype.size method to query the number

of mapping/elements. It's not in the strawman. It appears in the latest draft, though weirdly

enough Map.prototype.size is a function with an unused argument. What about making it an getter instead of a function? I guess we also don't

need the extra parens to request for Map#keys, Map#values, Map#items.

For keys, values and items I think they are cleaner as methods since they return a new iterator every time.

Agreed.

Dave

I buy making Map/Set size an accessor. This will be the first such property in the ES specification and it would be good to have some guidelines to help us make consistent decisions about using accessors in the future.

Nit: the poisoned "caller" "callee" and "arguments" properties on non-strict functions and arguments are specified as accessors.

# Brendan Eich (13 years ago)

David Bruant wrote:

2012/10/15 Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>>

* get/set accessor may have effects on 'set' (see the DOM) but
only on the receiver object (and unobservably,

I think that "unobservably" will be very hard (if not impossible in most cases) to achieve with proxies. "Unobservable except to the receiver handler if the receiver is a proxy" sounds like a more achievable constraint.

Ok.

any children that become garbage, e.g. when trimming .length on an
array-like).

I'm not sure I understand this part. Did you finish your sentence? What does "children" mean here?

No, I meant truncating an array-like by setting .length to 0 may make garbage but (absent some weak-ref turn-based notification system) it can't be observed.

# Brendan Eich (13 years ago)

Yehuda Katz wrote:

* get/set accessor may have effects on 'set' (see the DOM) but
only on the receiver object (and unobservably, any children that
become garbage, e.g. when trimming .length on an array-like).

DOM methods like innerHTML= seem to violate this particular wording (but perhaps not the spirit?)

Definitely the spirit!

That's one of those "children become garbage" cases, unless I misunderstand your point. "The receiver" for .innerHTML includes all descendants.

# Yehuda Katz (13 years ago)

Yehuda Katz (ph) 718.877.1325

On Mon, Oct 15, 2012 at 5:41 PM, Brendan Eich <brendan at mozilla.org> wrote:

Yehuda Katz wrote:

* get/set accessor may have effects on 'set' (see the DOM) but
only on the receiver object (and unobservably, any children that
become garbage, e.g. when trimming .length on an array-like).

DOM methods like innerHTML= seem to violate this particular wording (but perhaps not the spirit?)

Definitely the spirit!

That's one of those "children become garbage" cases, unless I misunderstand your point. "The receiver" for .innerHTML includes all descendants.

Yeah that makes sense, although the exact side-effects might be more observable with DOM. Relatedly, are we specifically exempting Object.observe and DOM Mutation observers from the "observability" requirement?

# Brendan Eich (13 years ago)

Yehuda Katz wrote:

On Mon, Oct 15, 2012 at 5:41 PM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

Yehuda Katz wrote:


        * get/set accessor may have effects on 'set' (see the DOM) but
        only on the receiver object (and unobservably, any
    children that
        become garbage, e.g. when trimming .length on an array-like).


    DOM methods like `innerHTML=` seem to violate this particular
    wording (but perhaps not the spirit?)


Definitely the spirit!

That's one of those "children become garbage" cases, unless I
misunderstand your point. "The receiver" for .innerHTML includes
all descendants.

Yeah that makes sense, although the exact side-effects might be more observable with DOM. Relatedly, are we specifically exempting Object.observe and DOM Mutation observers from the "observability" requirement?

Yes, or at least: I wasn't considering those (or Proxies, as David pointed out) in my "first cut".

Some of this must be excluded from any such "Design Rules".

# Allen Wirfs-Brock (13 years ago)

On Oct 15, 2012, at 2:41 PM, Brendan Eich wrote:

Yehuda Katz wrote:

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, any children that become garbage, e.g. when trimming .length on an array-like).

DOM methods like innerHTML= seem to violate this particular wording (but perhaps not the spirit?)

Definitely the spirit!

That's one of those "children become garbage" cases, unless I misunderstand your point. "The receiver" for .innerHTML includes all descendants.

It may be observable in another way: capture a reference to an existing inner element, then use innerHTML to change the parent element's inner element collection. Then use the original captured reference to navigate to its original parent. I'm not sure what innerHTML actually does in this case, but most alternatives would make it either directly or indirectly observable via the captured element reference that the original parent has been modified via innerHTML.

By my statement of the design guideline, innerHTML should not be an accessor. However, it is legacy and I was only accessing for guidelines for use in EcmaScript standards. It would be nice if HMTL APIs used the same guidelines but that isn't something that TC-39 could enforce, even if we wanted to.

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

On Oct 15, 2012, at 2:41 PM, Brendan Eich wrote:

Yehuda Katz wrote:

* get/set accessor may have effects on 'set' (see the DOM) but
only on the receiver object (and unobservably, any children that
become garbage, e.g. when trimming .length on an array-like).

DOM methods like innerHTML= seem to violate this particular wording (but perhaps not the spirit?) Definitely the spirit!

That's one of those "children become garbage" cases, unless I misunderstand your point. "The receiver" for .innerHTML includes all descendants.

It may be observable in another way: capture a reference to an existing inner element, then use innerHTML to change the parent element's inner element collection. Then use the original captured reference to navigate to its original parent. I'm not sure what innerHTML actually does in this case,

This test:

<script> function doit() { var x = document.getElementById('x'); var y = document.getElementById('y'); alert(y.parentNode); x.innerHTML = "<div id='z'>bye!</div>"; alert(y.parentNode); } </script> <body onload="doit()"> <div id="x"> Hi <div id="y">there</div> </div> </body>

displays

Hi there

then alerts

[object HTMLDivElement]

and then (once you dismiss that alert) displays

bye!

and alerts

null

So you're right, orphaned children suffer mutation to null .parentNode -- legacy feature indeed!

but most alternatives would make it either directly or indirectly observable via the captured element reference that the original parent has been modified via innerHTML.

By my statement of the design guideline, innerHTML should not be an accessor. However, it is legacy and I was only accessing for guidelines for use in EcmaScript standards. It would be nice if HMTL APIs used the same guidelines but that isn't something that TC-39 could enforce, even if we wanted to.

Legacy no one likes tends to fade, but innerHTML is pretty useful. If it had been a method, setInnerHTML, would it really be that much better? Is anything like it brewing in the modern DOM4/DOMCore/whatever-it's-called standards group?

# Allen Wirfs-Brock (13 years ago)

On Oct 15, 2012, at 9:20 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

...

So you're right, orphaned children suffer mutation to null .parentNode -- legacy feature indeed!

but most alternatives would make it either directly or indirectly observable via the captured element reference that the original parent has been modified via innerHTML.

By my statement of the design guideline, innerHTML should not be an accessor. However, it is legacy and I was only accessing for guidelines for use in EcmaScript standards. It would be nice if HMTL APIs used the same guidelines but that isn't something that TC-39 could enforce, even if we wanted to.

Legacy no one likes tends to fade, but innerHTML is pretty useful. If it had been a method, setInnerHTML, would it really be that much better? Is anything like it brewing in the modern DOM4/DOMCore/whatever-it's-called standards group?

Don't know, if anything new is brewing. Maybe Alex Russell is working one something...

Does it matter a lot that innerHTML doesn't follow this guideline? Not really. You can't depend upon best practices being followed by the world at large, but they're still useful to have. A set of best practice guidelines (at least for use within Ecma defined built-ins) for choosing when to use use accessors will help us make the overall set of built-in APIs more consistent as it grows. Otherwise, in situations like this you may get what appears to be coin-flip API design. Some objects may use an accessor and others a method in very comparable situations. A set of guidelines gives us a basis for making consistent decisions that can be explained. "no side-effects visible via other accessible objects" seems like reasonable candidate for an accessor best practice, even if innerHTML and many developer defined objects don't follow it.

But, it is just one of several possible accessor guidelines and it wouldn't necessarily be a bad thing if we don't adopt it. But we should have some guidelines so we don't appear to be coin-flip designers

# Erik Arvidsson (13 years ago)

On Mon, Oct 15, 2012 at 12:23 PM, Brendan Eich <brendan at mozilla.org> wrote:

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, any children that become garbage, e.g. when trimming .length on an array-like).

That is very limiting, even as a guideline. Any time there are two or more related objects it is very likely that a setter might affect some other object.

# Yehuda Katz (13 years ago)

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

This is similar to certain DOM APIs, and my expectation of a hypothetical version of Ember Data in ES6 would work. I don't think there is anything wrong with using an accessor here.

Yehuda Katz (ph) 718.877.1325

# Brendan Eich (13 years ago)

Erik Arvidsson wrote:

On Mon, Oct 15, 2012 at 12:23 PM, Brendan Eich<brendan at mozilla.org> wrote:

  • get/set accessor may have effects on 'set' (see the DOM) but only on the receiver object (and unobservably, any children that become garbage, e.g. when trimming .length on an array-like).

That is very limiting, even as a guideline. Any time there are two or more related objects it is very likely that a setter might affect some other object.

You're right, and my children-may-become-garbage loophole wasn't wide enough. But children-might-be-orphaned-with-null-parentNode is not bad, as a wider loophole. Are we really talking about significant [*] unrelated changes outside the container whose setter was invoked?

/be

[*] Sure, lots of things can change, especially due to events, even ignoring observers and such. Events bubbling up was something I left out altogether.

# Brendan Eich (13 years ago)

Yehuda Katz wrote:

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

I'm with Allen: you shoulda used a method!

I know, this is all allowed, so it will happen. We're talking "Design Rules" here, which the language cannot enforce. But really, too much spooky setter action at a distance, even for bidirectionally-linked objects.

# Yehuda Katz (13 years ago)

Yehuda Katz (ph) 718.877.1325

On Tue, Oct 16, 2012 at 5:13 PM, Brendan Eich <brendan at mozilla.org> wrote:

Yehuda Katz wrote:

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

I'm with Allen: you shoulda used a method!

I feel somewhat strongly that this is an appropriate use of a setter. Using a method would give the API a static language feel for not enough win. In the case of a well-defined API with well-understood links between objects, a setter feels right to me.

In fact, the entire point of Object.observe as an API is to produce side-effects of setting data properties. Making it asynchronous solves the programming hazards, but doesn't change the larger philosophical point. Data bindings and similar systems general produce side effects from setters, and I am ok with that.

# Allen Wirfs-Brock (13 years ago)

On Oct 16, 2012, at 1:25 PM, Yehuda Katz wrote:

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

A great example of where you should not use an accessor. As a rough guideline accessors (which are syntactically models after state attributes) should be used to set/query local characteristics of objects. Methods should be use for complex computations and for establishing and accessing complex relationships among objects. Why? Because experience with complex object models have shown that you have more understandable/extensible/maintainable systems when you are careful about where and how you expose coupling among objects.

In this case, there is a bi-directional n-to-one relationship being maintained between objects. Such complex relationships are best modeled using methods because their require establishing and maintaining the relationship requires state changes in multiple objects. Expressing the operation as a method serves as a warning that something more complex than simply updating some local state is going on.

This is similar to certain DOM APIs, and my expectation of a hypothetical version of Ember Data in ES6 would work. I don't think there is anything wrong with using an accessor here.

I don't think anybody hold the DOM up as a stelar example of Object-oriented design.

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your guidelines for choosing to use an accessor rather than a method? If you consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

# Tab Atkins Jr. (13 years ago)

On Tue, Oct 16, 2012 at 2:24 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Oct 16, 2012, at 1:25 PM, Yehuda Katz wrote:

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

A great example of where you should not use an accessor. As a rough guideline accessors (which are syntactically models after state attributes) should be used to set/query local characteristics of objects. Methods should be use for complex computations and for establishing and accessing complex relationships among objects. Why? Because experience with complex object models have shown that you have more understandable/extensible/maintainable systems when you are careful about where and how you expose coupling among objects.

In this case, there is a bi-directional n-to-one relationship being maintained between objects. Such complex relationships are best modeled using methods because their require establishing and maintaining the relationship requires state changes in multiple objects. Expressing the operation as a method serves as a warning that something more complex than simply updating some local state is going on.

This is similar to certain DOM APIs, and my expectation of a hypothetical version of Ember Data in ES6 would work. I don't think there is anything wrong with using an accessor here.

I don't think anybody hold the DOM up as a stelar example of Object-oriented design.

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your guidelines for choosing to use an accessor rather than a method? If you consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

I consider Yehuda's example to be a perfectly fine and completely normal place to use an accessor. Accessing the list of comments attached to a post is conceptually a side-effect-free operation. While it can change over time, it only does so due to obvious actions

  • querying it twice, one immediately after the other, will give === results.

Setting the attribute may have side-effects, but reasonable ones (setting/unsetting Comments.post or Post.comments on other objects). This is a reasonable thing for a setter to do.

I don't see any reason to make these two into methods.

# Allen Wirfs-Brock (13 years ago)

On Oct 16, 2012, at 2:36 PM, Tab Atkins Jr. wrote:

On Tue, Oct 16, 2012 at 2:24 PM, Allen Wirfs-Brock

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your guidelines for choosing to use an accessor rather than a method? If you consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

I consider Yehuda's example to be a perfectly fine and completely normal place to use an accessor. Accessing the list of comments attached to a post is conceptually a side-effect-free operation. While it can change over time, it only does so due to obvious actions

  • querying it twice, one immediately after the other, will give === results.

Setting the attribute may have side-effects, but reasonable ones (setting/unsetting Comments.post or Post.comments on other objects). This is a reasonable thing for a setter to do.

I don't see any reason to make these two into methods.

So, what are the design guidelines that you would apply leads to these being accessors? When do you use a method? When do you use an accessor?

# Tab Atkins Jr. (13 years ago)

On Tue, Oct 16, 2012 at 2:43 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Oct 16, 2012, at 2:36 PM, Tab Atkins Jr. wrote:

On Tue, Oct 16, 2012 at 2:24 PM, Allen Wirfs-Brock

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your guidelines for choosing to use an accessor rather than a method? If you consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

I consider Yehuda's example to be a perfectly fine and completely normal place to use an accessor. Accessing the list of comments attached to a post is conceptually a side-effect-free operation. While it can change over time, it only does so due to obvious actions

  • querying it twice, one immediately after the other, will give === results.

Setting the attribute may have side-effects, but reasonable ones (setting/unsetting Comments.post or Post.comments on other objects). This is a reasonable thing for a setter to do.

I don't see any reason to make these two into methods.

So, what are the design guidelines that you would apply leads to these being accessors? When do you use a method? When do you use an accessor?

As getters, I think it's self-explanatory. They're idempotent and effect-free.

As setters, they do go beyond what Brendan suggested in his original formulation, but the effects still feel reasonably "local" to me. Whether the setter is considered to have non-local effects or not is actually just a question of implementation, not essence - we could implement the setter simply and naively, and have the getters do an expensive tree traversal to assemble the answer, or have the getter be simple and naive, and have the setter reach in and do some mutations to maintain the state of the tree.

# Yehuda Katz (13 years ago)

Yehuda Katz (ph) 718.877.1325

On Tue, Oct 16, 2012 at 5:43 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Oct 16, 2012, at 2:36 PM, Tab Atkins Jr. wrote:

On Tue, Oct 16, 2012 at 2:24 PM, Allen Wirfs-Brock

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your

guidelines for choosing to use an accessor rather than a method? If you

consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

I consider Yehuda's example to be a perfectly fine and completely normal place to use an accessor. Accessing the list of comments attached to a post is conceptually a side-effect-free operation. While it can change over time, it only does so due to obvious actions

  • querying it twice, one immediately after the other, will give === results.

Setting the attribute may have side-effects, but reasonable ones (setting/unsetting Comments.post or Post.comments on other objects). This is a reasonable thing for a setter to do.

I don't see any reason to make these two into methods.

So, what are the design guidelines that you would apply leads to these being accessors? When do you use a method? When do you use an accessor?

One possible approach would be to have clear design guidelines for getters, and allow setters with reasonable observable side-effects for getters that comply. That is the approach I use in my designs.

# Mark S. Miller (13 years ago)

Getting the comments with a getter seems fine. Appending only the list of comments with a setter is bad, as it does not resemble storage semantics. I would expect well written setters to at least be idempotent, and well written getters to be side effect free.

# Yehuda Katz (13 years ago)

On Tue, Oct 16, 2012 at 6:26 PM, Mark S. Miller <erights at google.com> wrote:

Getting the comments with a getter seems fine. Appending only the list of comments with a setter is bad, as it does not resemble storage semantics.

Do you mean appending only to to list of comments?

comment.post = post has similar semantics to setting a foreign key in a SQL database, which will (obviously) update future requests for "all comments with post_id=post.id". Bidirectionally linked one-to-many relationships are pretty common, and it's desirable for the relationships to remain in sync automatically. I don't think this is the right heuristic for moving to a method.

I would expect well written setters to at least be idempotent, and well

written getters to be side effect free.

comment.post = post is, of course idempotent. Setting it to the same value as it already has would have no effect.

On Tue, Oct 16, 2012 at 2:36 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

On Tue, Oct 16, 2012 at 2:24 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Oct 16, 2012, at 1:25 PM, Yehuda Katz wrote:

Agreed. For example:

class Post {

}

class Comment {

}

Post.hasMany("comments"); Comment.belongsTo("post");

let post = new Post() let comment = new Comment();

comment.post = post; post.comments //=> [comment]

A great example of where you should not use an accessor. As a rough guideline accessors (which are syntactically models after state attributes) should be used to set/query local characteristics of objects. Methods should be use for complex computations and for establishing and accessing complex relationships among objects. Why? Because experience with complex object models have shown that you have more understandable/extensible/maintainable systems when you are careful about where and how you expose coupling among objects.

In this case, there is a bi-directional n-to-one relationship being maintained between objects. Such complex relationships are best modeled using methods because their require establishing and maintaining the relationship requires state changes in multiple objects. Expressing the operation as a method serves as a warning that something more complex than simply updating some local state is going on.

This is similar to certain DOM APIs, and my expectation of a hypothetical

version of Ember Data in ES6 would work. I don't think there is anything

wrong with using an accessor here.

I don't think anybody hold the DOM up as a stelar example of Object-oriented design.

You might not agree with the above guideline, or choose to follow it. That's fine. But, what you you propose as an alternative. What are your guidelines for choosing to use an accessor rather than a method? If you consider you example to be a reflection of "good design" what are the specific design principles it is following. Why is that an appropriate place to use an accessor.

I consider Yehuda's example to be a perfectly fine and completely normal place to use an accessor. Accessing the list of comments attached to a post is conceptually a side-effect-free operation. While it can change over time, it only does so due to obvious actions

  • querying it twice, one immediately after the other, will give === results.

Setting the attribute may have side-effects, but reasonable ones (setting/unsetting Comments.post or Post.comments on other objects). This is a reasonable thing for a setter to do.

I don't see any reason to make these two into methods.

~TJ


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

-- Cheers, --MarkM


es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss

Yehuda Katz (ph) 718.877.1325

# Brendan Eich (13 years ago)

Yehuda Katz wrote:

I feel somewhat strongly that this is an appropriate use of a setter. Using a method would give the API a static language feel for not enough win. In the case of a well-defined API with well-understood links between objects, a setter feels right to me.

I'm not super-religious here. In fact I suspect we (TC39, or any non-trivially small group formed from es-discuss participants) will not agree on "one true way".

JS is multi-paradigm. We're not as TimToady (en.wikipedia.org/wiki/There's_more_than_one_way_to_do_it) as Perl but we are definitely not narrow and pedagogical and prescriptive.

So maybe we should look for least common denominator rules. Map/Set.prototype.size as accessor still wins by any such.

# Tab Atkins Jr. (13 years ago)

On Tue, Oct 16, 2012 at 4:06 PM, Yehuda Katz <wycats at gmail.com> wrote:

On Tue, Oct 16, 2012 at 6:26 PM, Mark S. Miller <erights at google.com> wrote:

Getting the comments with a getter seems fine. Appending only the list of comments with a setter is bad, as it does not resemble storage semantics.

Do you mean appending only to to list of comments?

comment.post = post has similar semantics to setting a foreign key in a SQL database, which will (obviously) update future requests for "all comments with post_id=post.id". Bidirectionally linked one-to-many relationships are pretty common, and it's desirable for the relationships to remain in sync automatically. I don't think this is the right heuristic for moving to a method.

Yup; as I argued, the setter only has "non-local" effects if you're using denormalized data (which you almost certainly will be, because it makes the more-common reads more efficient). It's definitely possible to implement identical behavior by having the setter only have a local effect, and having the getter be an expensive traversal.

# Alex Russell (13 years ago)

I agree (unsurprisingly) with Arv and Yehuda on this. Side effects are what make the world go 'round. Getting overly prescriptive here is just a way to box us into [not]using some particular stylistic form when designing API...and I don't see how that settles any interesting questions. I'd much rater we have debates as they arise about what is most idiomatic/performant/etc. We can use the weight of precedent to inform those discussions, but I'm pretty shocked that we would want to converge on hard/fast rules about accessors.

# Mark S. Miller (13 years ago)

On Tue, Oct 16, 2012 at 4:06 PM, Yehuda Katz <wycats at gmail.com> wrote:

On Tue, Oct 16, 2012 at 6:26 PM, Mark S. Miller <erights at google.com>wrote:

Getting the comments with a getter seems fine. Appending only the list of comments with a setter is bad, as it does not resemble storage semantics.

Do you mean appending only to to list of comments?

Yes.

comment.post = post has similar semantics to setting a foreign key in a SQL database, which will (obviously) update future requests for "all comments with post_id=post.id". Bidirectionally linked one-to-many relationships are pretty common, and it's desirable for the relationships to remain in sync automatically. I don't think this is the right heuristic for moving to a method.

I would expect well written setters to at least be idempotent, and well

written getters to be side effect free.

comment.post = post is, of course idempotent. Setting it to the same value as it already has would have no effect.

Good. In light of this, I reread the original post of your example. I had misunderstood it.

I strongly agree with those earlier posts saying that we should have some well thought out design guidelines for when to use an accessor vs a method, and that accessor usage should only be "storage-like". However, now that I understand it, I also agree that your example is storage-like enough to my eyes that I would like guidelines that permit it as a normal case. I do not yet a concrete suggestion other than "getters should be query-only" and "setters should be idempotent and not cause 'external' effects (other than notifying registered observers)." The key thing about your example is "What does external mean?" I don't yet have an answer to offer.

These guidelines are not prescriptive on any external JS community. But we need guidelines for APIs designed by TC39 and (hopefully eventually) by w3c.

# Yehuda Katz (13 years ago)

On Wed, Oct 17, 2012 at 11:16 AM, Mark S. Miller <erights at google.com> wrote:

On Tue, Oct 16, 2012 at 4:06 PM, Yehuda Katz <wycats at gmail.com> wrote:

On Tue, Oct 16, 2012 at 6:26 PM, Mark S. Miller <erights at google.com>wrote:

Getting the comments with a getter seems fine. Appending only the list of comments with a setter is bad, as it does not resemble storage semantics.

Do you mean appending only to to list of comments?

Yes.

comment.post = post has similar semantics to setting a foreign key in a SQL database, which will (obviously) update future requests for "all comments with post_id=post.id". Bidirectionally linked one-to-many relationships are pretty common, and it's desirable for the relationships to remain in sync automatically. I don't think this is the right heuristic for moving to a method.

I would expect well written setters to at least be idempotent, and well

written getters to be side effect free.

comment.post = post is, of course idempotent. Setting it to the same value as it already has would have no effect.

Good. In light of this, I reread the original post of your example. I had misunderstood it.

I strongly agree with those earlier posts saying that we should have some well thought out design guidelines for when to use an accessor vs a method, and that accessor usage should only be "storage-like". However, now that I understand it, I also agree that your example is storage-like enough to my eyes that I would like guidelines that permit it as a normal case. I do not yet a concrete suggestion other than "getters should be query-only" and "setters should be idempotent and not cause 'external' effects (other than notifying registered observers)." The key thing about your example is "What does external mean?" I don't yet have an answer to offer.

These guidelines are not prescriptive on any external JS community. But we need guidelines for APIs designed by TC39 and (hopefully eventually) by w3c.

The reason for my examples was just to illustrate that if ES.future had an API similar to what I presented, I would want it to be an accessor. As a result, I disagreed with the original proposed guideline.

I like the idempotent requirement, but I'm not sure I agree with the observer distinction. Especially if the observer is behind the abstraction boundary, but has observable external side effects (exactly what is happening in my example), it seems indistinguishable from doing the work in an accessor.