(Map|Set|WeakMap)#set() returns `this` ?
On Mon, Dec 3, 2012 at 4:28 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
I wonder what was the use case that convinced TC39 to return
this
with these methods.
Assuming you read the notes, I proposed the agenda item based on the best practice of ensuring meaningful returns, and in the case of mutation methods, |this| is a meaningful return.
Accordingly, this will never work: var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery'));
Previously, map.set() had a useless void return...
And it will be something like: var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery')).get('queried');
which is ugly and I don't really understand where map.set(k0, v0).set(k1, v1).set(k2, v2) could be useful.
Accessing the object post-mutation allows for more expressive use of the API.
IMHO, a set(key, value) should return the value as it is when you address a value
var o = m.get(k) || m.set(k, v); // o === v
// equivalent of
var o = m[k] || (m[k] = v); // o === v
a set with a key that returns this
is a non case so almost as useless as
the void return is.
Usefulness comes with use cases ... except this jQuery chainability thingy that works fine for jQuery structure ( an ArrayLike Collection ) who asked for map.set(k0, v0).set(k1, v1).set(k2, v2) ? Or even map.set(k0,v0).get(k1) ? what are use cases for this?
I am honestly curious about them because I cannot think a single one ... specially with the Set
s.add(k0).add(k1).add(k2) ... this code looks weird inlined like this ...
Thanks for your patience
On Mon, Dec 3, 2012 at 5:21 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
IMHO, a set(key, value) should return the value as it is when you address a value
var o = m.get(k) || m.set(k, v); // o === v
// equivalent of
var o = m[k] || (m[k] = v); // o === v
a set with a key that returns
this
is a non case so almost as useless as the void return is.
Usefulness comes with use cases ... except this jQuery chainability thingy that works fine for jQuery structure ( an ArrayLike Collection )
A collection is a collection.
who asked for map.set(k0, v0).set(k1, v1).set(k2, v2) ? Or even
map.set(k0,v0).get(k1) ? what are use cases for this?
I am honestly curious about them because I cannot think a single one ... specially with the Set
s.add(k0).add(k1).add(k2) ... this code looks weird inlined like this ...
You're completely ignoring the iterator APIs and forEach—either of which a program might want to call on an object post-mutation:
Add value to the Set and...
-
get a fresh iterable for the values (or keys, or entries):
set.add( value ).values();
-
send each value in the set to another operation:
set.add( value ).forEach( item => ...send to some operation.... );
-
spread into an array of unique items:
[ ...set.add(value) ]; // always unique! yay!
Add a key and value to the Map and...
-
get a fresh iterable for the keys (or values, or entries)
map.set( key, val ).keys(); map.set( key, val ).values(); map.set( key, val ).entries();
-
send each to pair to another operation (see above)
-
spread into an array of pairs (see above)
Being able to express the complete operation and get mutated object back at once is a compelling use case.
fair enough ... but here there was a typo, right?
set.add( value ).forEach( item => ...send to some operation.... );
I'm not a person of influence, but as a JS developer, I agree with Andrea on this. I think Map#set() should return the value. I would expect the same behavior as obj[key] = value. I find Andrea's use case (m.get(k) || m.set(k, v)) more compelling than the method chaining possibilities.
Nathan
Date: Mon, 3 Dec 2012 14:21:24 -0800
Subject: Re: (Map|Set|WeakMap)#set() returns this
?
From: andrea.giammarchi at gmail.com
To: waldron.rick at gmail.com
CC: es-discuss at mozilla.org
IMHO, a set(key, value) should return the value as it is when you address a value var o = m.get(k) || m.set(k, v); // o === v // equivalent of var o = m[k] || (m[k] = v); // o === v
a set with a key that returns this
is a non case so almost as useless as the void return is.
Usefulness comes with use cases ... except this jQuery chainability thingy that works fine for jQuery structure ( an ArrayLike Collection ) who asked for map.set(k0, v0).set(k1, v1).set(k2, v2) ? Or even map.set(k0,v0).get(k1) ? what are use cases for this?
I am honestly curious about them because I cannot think a single one ... specially with the Set s.add(k0).add(k1).add(k2) ... this code looks weird inlined like this ...
Thanks for your patience
On Mon, Dec 3, 2012 at 2:04 PM, Rick Waldron <waldron.rick at gmail.com> wrote:
On Mon, Dec 3, 2012 at 4:28 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
I wonder what was the use case that convinced TC39 to return this
with these methods.
Assuming you read the notes, I proposed the agenda item based on the best practice of ensuring meaningful returns, and in the case of mutation methods, |this| is a meaningful return.
Accordingly, this will never work:var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery'));
Previously, map.set() had a useless void return...
And it will be something like:var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery')).get('queried');
which is ugly and I don't really understand where map.set(k0, v0).set(k1, v1).set(k2, v2) could be useful.
Accessing the object post-mutation allows for more expressive use of the API.
Rick
Thanks for clarifications ( a use case would be already good )
br
I meant the forEach on a Set which I've never seen before in specs ... ;-)
for what is worth it, my use case is exactly this:
var value = map.has(key) ? map.get(key) : map.set(key, someValue);
which could be inlined easily and it looks less ugly and/or redundant than:
(map.has(key) ? map.get(key) : map.set(key, someValue).get(key))
but I see Rick use cases too ... probably not my style thought
crap ... this stuff is not in V8 thought, not in node --harmony at least ... I need to update my polyfills .. !
+1 (to all sentiments in the message :)
On Dec 3, 2012, at 2:53 PM, Nathan Wall wrote:
I'm not a person of influence, but as a JS developer, I agree with Andrea on this. I think Map#set() should return the value. I would expect the same behavior as obj[key] = value. I find Andrea's use case (m.get(k) || m.set(k, v)) more compelling than the method chaining possibilities.
It's also worth noting that in Smalltalk the at:put: methods that roughly correspond to ES6 set methods always return the value that is stored. This is in a language where the default behavior is to return the "this value" of a method.
Smalltalk programmers use "this chaining" a lot, but in the case of at:put: the stored value is deemed the more interesting value to return. In reality it depends upon your use case. If you want to do this chaining then you want the this value as the result. If you want to pass a computed stored value on to something else, you want the stored value returned. You can't have it both ways. Smalltalk had another way to do this chaining that ignores the actual return value, so returning the stored value was an obvious design choice for it.
It's less clear which is the best choice for JS.
clear for "us" ( Developers ) but also clear semantically speaking.
As it is proposed now it looks like if a "setter" returns something which is the context while, again, when you set 99% of the time you don't want to do it separately in order to have that value back.
You don't want to create another reference and you want "set" to do what you expect: set
When you set, you implicitly point to the set value as it is for:
return some.prop = someValue;
var setValue = (obj.prop = someValue);
var deflt = obj.prop || (obj.prop = value);
this.doStuff( this.hasOwnProperty(key) ? this[key] : this[key] = computateThingOnce() );
And so on ... I think there are tons of use cases more frequent and used than this.set(key, value).get(key) ... I wonder how many times you have dreamed about var value = arr.push(some[data]); too and going back to jQuery style, when you add something, the equivalent of set or add for Set, you point to the subset you have added, not the initial collection, isn't it ... api.jquery.com/add
So developers prefer the added/set value, this is why I have asked who/why
you decided for that, IMHO pointless, this
as default return.
It just does not look right but sure it might have some use case ... the fact I had to think about them means already these are more rare than a classic returned value.
+1 Smalltalk choice then
Allen Wirfs-Brock wrote:
It's less clear which is the best choice for JS.
I have to say I think Mark is on the better track (not to say "only right track"). Cascading wants its own special form, e.g., Dave's mustache-repurposed proposal at
blog.mozilla.org/dherman/2011/12/01/now-thats-a-nice-stache
so one can write cascades without having to be sure the methods involved follow an unchecked |this|-returning convention.
This frees the set return value pigeon-hole to be what many people naturally want, from Smalltalk to JS (just citing experience). In the meeting, I heard |this|-return asserted as a dominant pattern, but I don't believe it is -- especially not for collection.set() methods.
On Tue, Dec 4, 2012 at 1:55 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
clear for "us" ( Developers ) but also clear semantically speaking.
As it is proposed now it looks like if a "setter" returns something which is the context while, again, when you set 99% of the time you don't want to do it separately in order to have that value back.
You don't want to create another reference and you want "set" to do what you expect: set
When you set, you implicitly point to the set value as it is for:
return some.prop = someValue;
var setValue = (obj.prop = someValue);
var deflt = obj.prop || (obj.prop = value);
this.doStuff( this.hasOwnProperty(key) ? this[key] : this[key] = computateThingOnce() );
And so on ... I think there are tons of use cases more frequent and used than this.set(key, value).get(key) ... I wonder how many times you have dreamed about var value = arr.push(some[data]); too
My dream would be that push returned the array, so that I could immediately operate on that array.
and going back to jQuery style, when you add something, the equivalent of set or add for Set, you point to the subset you have added, not the initial collection, isn't it ... api.jquery.com/add
No, this returns a new jQuery object (ie. the array-like jQuery collection of elements) that is the initial object merged with the newly added item (pushed onto the end) and filtered for uniqueness:
var elems = jQuery("body");
elems.add("div:first"); // [ body, div ]
elems.add("div:first"); // [ body, div ] <-- "looks" the same
The creation of a new jQuery object (via pushStack) is a legacy mechanism—however the observable result is effectively the same as set.add(something) => set, as elements.add(element) => newElements (as far
as any web developer's program semantics are concerned).
So developers prefer the added/set value,
Three vocal posters to a mailing list do not represent "developers" as a whole. There were more web developer originating committee members that supported returning this. (I hate playing this game, it's not fun)
this is why I have asked who/why you decided for that, IMHO pointless,
this
as default return.
I answered this directly in the first response. I proposed it, based on a wealth of existing library code used on the web and the proposal was met with significant support from within the committee.
It just does not look right but sure it might have some use case ... the fact I had to think about them means already these are more rare than a classic returned value.
+1 Smalltalk choice then
All due respect, but JavaScript is not Smalltalk. Let's standardize on our own established patterns.
for develoeprs I meant jQuery users too, being one of th emost popular API out there.
What I meant with jQuery#add method is that last thing added is the one returned, it does nto return the initial object, it returns the new result out of a merge but this is not the initial this, this is a new thing with latest added thing in.
Br
On Tue, Dec 4, 2012 at 2:46 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
for develoeprs I meant jQuery users too, being one of th emost popular API out there.
What I meant with jQuery#add method is that last thing added is the one returned, it does nto return the initial object, it returns the new result out of a merge but this is not the initial this, this is a new thing with latest added thing in.
That is exactly what I described—the case for returning a fresh jQuery object exists to support end() (api.jquery.com/end) which allows you to chain operations (eg. filter->apply css or something) and restore
the original jQuery object (matching set of elements) by keeping a reference to that object stored as a property of the new jQuery object. This mechanism is irrelevant in the comparison of Set API semantics.
it would be nice to add a #put(key, value) that returns value and see what developers prefer on daily basis tasks :-)
anyway, if it won't change, it's OK, I had my answer,
On Tue, Dec 4, 2012 at 3:07 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
it would be nice to add a #put(key, value) that returns value and see what developers prefer on daily basis tasks :-)
anyway, if it won't change, it's OK, I had my answer, thanks
I like surveying actual developer-users like this, despite the committee's aversion to "design-by-survey". I sent out a survey 2 weeks ago and received 381 responses, 256 for return-this and 125 for return something else (undefined, the new length or size, the value). If the results had been different, I would've removed the item from the agenda entirely, but they are as I expected them to be and I feel compelled to take that into consideration. This survey was not mentioned as a part of my proposal and therefore had no influence on the decision made by the committee.
The survey I'd like to see is among underscore, jQuery, and other popular libraries (go by NPM dependency frequency and client-side use frequency), how do set-like methods work: return-this or return-v?
This is closer than a people-survey to mapping the cowpath.
agreed ... but then these analysis should be done before making decisions in TC39 meeting notes.
If this was the case then, as I have said, I am OK with the decision ( de gustibus )
br
On Tue, Dec 4, 2012 at 3:50 PM, Brendan Eich <brendan at mozilla.org> wrote:
The survey I'd like to see is among underscore, jQuery, and other popular libraries (go by NPM dependency frequency and client-side use frequency), how do set-like methods work: return-this or return-v?
underscore and jQuery side with
- returning the calling object
- returning a new object of the same "kind", but with the results of the operation.
jQuery objects themselves are similar to a set, most operations on the set itself result in returning a new jQuery object (set) that reflects the results of the operation. As I explained previously, the reason it returns a new jQuery object is to support the end() and addBack() methods by storing the original as a property of the current, this that allows either restoring the original set to it's pre-operation state or adding the original set to the current set. If this were not an API that we supported, jQuery could just as easily be a custom wrapper over an ES6 Set.
underscore actually has a special method called _.chain() that returns a wrapped object that allows for chaining all of the underscore APIs—until value() is called, which closely matches the examples I gave yesterday. underscore has a _.defaults() function that returns the mutated object after applying a default set of properties and values (similar to _.extend()). There is no analogous Set api in underscore.
for npm in order of most dependencies:
-
underscore (see above).
-
request.
- Any API that "sets" something, returns this ( mikeal/request/blob/master/main.js#L735-L758 )
- There is a lib that Mikeal also wrote "form-data" that has an append method that does not return this
-
async. Nothing comparable
-
express.
- app.set, and subsequently all aliases, returns this ( visionmedia/express/blob/master/lib/application.js#L250-L261 )
- response.set, and subsequently all aliases, returns this ( visionmedia/express/blob/master/lib/response.js#L37-L40 )
-
optimist.
- every operation that is not a "get" returns self as this; ( substack/node-optimist/blob/master/index.js)
-
commander.
- nearly every operation that is not a "get" returns this; ( visionmedia/commander.js/blob/master/index.js)
-
colors
- setTheme returns the theme that was set
-
uglify. Nothing comparable
-
connect.
- Too much to review, but it's primary API that "sets" values, returns this (see examples in readme)
- Too much to review, nearly everything returns this ( LearnBoost/socket.io/blob/master/lib/socket.js)
When I was researching whether or not it was worthwhile to pursue this discussion, I reviewed most of this stuff, as well as web-focused libs.
At the moment I have to head out, so I can't continue to research this, but I'll gladly spend as many hours as anyone thinks is necessary to prove my position.
On Mon, Dec 3, 2012 at 2:21 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
IMHO, a set(key, value) should return the value as it is when you address a value
var o = m.get(k) || m.set(k, v); // o === v
// equivalent of
var o = m[k] || (m[k] = v); // o === v
If this pattern is considered sufficiently useful (I think it is), we should handle it directly, as Python does. Python dicts have a setDefault(key, value) method which implements this pattern exactly - if the key is in the dict, it returns its associated value (acts like a plain get()); if it's not, it sets the key to the passed value and then returns it. Using this pattern is not only clearer, but avoids repetition (of "m" and "k" in your example), and actually chains - I use setDefault all the time when working with nested dicts.
(For example, if I have a sparse 2d structure implemented with nested dicts, I can safely get/set a terminal value with code like "a.setDefault(k1, dict()).set(k2, v)". If that branch hadn't been touched before, this creates the nested dict for me. If it has, I create a throwaway empty dict, which is cheap. If JS ever grows macros, you can avoid the junk dict as well.)
I prefer the plain methods to work as they are currently specified, where they return this.
as discussed before, the problem with setDefault() approach you are suggesting is that the dict(), at least in JS, will be created in any case.
var newDict = a.setDefault(k1, dict());
above operation will invoke dict() regardless k1 was present or less and this is not good for anyone: RAM, CPU, GC, etc
the initial pattern is and wants to be like that so that not a single pointless operation is performed when/if the key is already there.
var o = obj.has(key) ? obj.get(key) : obj.set(key, dict()); // <== see dict, never called if key
quick and dirty
var o = obj.get(key) || obj.set(key, dict());
With current pattern, and my only concern is that after this decision every
other set()
like pattern will return this even where not optimal, I have
to do
var o = obj.has(key) ? obj.get(key) : obj.set(key, dict()).get(key);
meh ... but I can survive :D
My 2 cents against the windmills...
I personally think returning this
in absence of any meaningful value (and
chaining in general) is a bad pattern. Chaining leads to worse readability
(there's nothing subjective about this, if you have to scan the code to
another page to figure out which object the code is interacting with, it's
bad readability) and returning this is more future-hostile than returning
nothing. If we in the future come up with something actually useful the
function could return, there's no turning back if it already returns
this
. For set(), meaningful values include the value that was set, a
boolean whether the value was added or replaced, etc., whereas this
is
meaningless since you have it already. Returning this
in absence of
meaningful values is also a mental overhead: "Does this return a meaningful
value, or just this, so can I chain it?"
I, like Andrea, have dreamed that Array#push() returned the value I passed to it (although the reason that it doesn't is probably that you can pass multiple values to push), I have a lot of cases where I've hoped that I could do this:
return objects.push({ foo: bar, baz: tar })
instead of:
var newObject = { foo: bar, baz: tar } objects.push(newObject) return newObject
For Array#push() to have returned this
, I can't think of a single line of
code it would have made simpler.
Another thing is that set() returning the value that was set is also
consistent with language semantics in that (a = 1)
has the value of 1.
I'd like to see a better way to do chaining than functions returning
this
, like the monocle mustache.
Date: Tue, 4 Dec 2012 11:03:57 -0800 From: brendan at mozilla.org Subject: Re: (Map|Set|WeakMap)#set() returns
this
?Allen Wirfs-Brock wrote:
It's less clear which is the best choice for JS.
Cascading wants its own special form, e.g., Dave's mustache-repurposed proposal at
blog.mozilla.org/dherman/2011/12/01/now-thats-a-nice-stache
so one can write cascades without having to be sure the methods involved follow an unchecked |this|-returning convention.
I really like this possibility. Is there any way of the monocle-mustache making it into ... say, ES7?
If so, it would seem wrong to ever return this
. Sounds like you get the best of both worlds to me!
Nathan
On Wed, Dec 5, 2012 at 10:06 AM, Nathan Wall <nathan.wall at live.com> wrote:
Date: Tue, 4 Dec 2012 11:03:57 -0800 From: brendan at mozilla.org
Subject: Re: (Map|Set|WeakMap)#set() returns
this
?Allen Wirfs-Brock wrote:
It's less clear which is the best choice for JS.
Cascading wants its own special form, e.g., Dave's mustache-repurposed proposal at
blog.mozilla.org/dherman/2011/12/01/now-thats-a-nice-stache
so one can write cascades without having to be sure the methods involved follow an unchecked |this|-returning convention.
I really like this possibility. Is there any way of the monocle-mustache making it into ... say, ES7?
If so, it would seem wrong to ever return
this
. Sounds like you get the best of both worlds to me!
Yes, monocle-mustache is very cool, especially Dave's proposed version here, but this:
obj.{ prop = "val" };
...has received negative feedback, because developers want colon, not equal, but colon is to "define" as equal is to "assign".
eg. What does this do?
elem.{ innerHTML: "<p>paragraph</p>" };
Most developers would naturally assume that this sets elem.innerHTML to "<p>paragraph</p>", but it actually results in a [[DefineOwnProperty]]
of innerHTML with {[[Value]]: "<p>paragraph</p>" , [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, which would blow away the accessor descriptor that was previously defined for elem.innerHTML (ie. the one that would convert "<p>paragraph</p>" to a node and insert it into the
DOM. So the obvious choice is to use "=" instead of ":" because it correctly connotes the assignment behaviour—except that developers complained about that when we evangelized the possibility.
Monocle-mustache is simply not a replacement for return this because chaining mutation methods is not the sole use case. Please review the use cases I provided earlier in the thread.
There is simply too much real world evidence (widely adopted libraries (web) and modules (node)) in support of return-this-from-mutation-method to ignore, or now go back on, a base criteria for including the pattern in newly designed built-in object APIs.
For what is worth it , I don't believe "everyone does it like that" has ever been a valid argument. Maybe everyone simply copied a pattern from jQuery without even thinking if it was needed or it was the best.
Br
On Wed, Dec 5, 2012 at 6:29 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
For what is worth it , I don't believe "everyone does it like that" has ever been a valid argument. Maybe everyone simply copied a pattern from jQuery without even thinking if it was needed or it was the best.
Br
Agreed. You don't make a band that makes crappy music because most other bands do too. You don't jump in the well if I do. We don't have to make bad API decisions because the cows have paved a path to the butcher's. It's not ignoring the norm, we acknowledge it and make our choices with that knowledge, but we don't necessarily have to follow the norm.
and as last argument from my side, I LOVE the DOM appendChild method and I use it a lot
var div = document.body.appendChild(document.createElement("div"));
This is the best example of "returning the value" I could think and the one I use the most on daily basis.
Br
On Wed, Dec 5, 2012 at 8:52 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
and as last argument from my side, I LOVE the DOM appendChild method and I use it a lot
var div = document.body.appendChild(document.createElement("div"));
This is the best example of "returning the value" I could think and the one I use the most on daily basis.
On the other hand, I find it constantly frustrating, because I always want to chain more operations (like more .appendChild() calls) off of it, and am instead forced to do them as separate statements. ^_^
On Wed, Dec 5, 2012 at 11:52 AM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
and as last argument from my side, I LOVE the DOM appendChild method and I use it a lot
var div = document.body.appendChild(document.createElement("div"));
This is the best example of "returning the value" I could think and the one I use the most on daily basis.
Arguably, the child element is a better meaningful return then this. That's ok, because I also don't think that every mutation method should only ever return this... just in places that encourage expressive use of the API and ease the pains of operating on objects post-mutation.
Incidentally, that's also a commonly "fixed" API:
YUI: yui/yui3/blob/master/src/node/js/node-create.js#L39-L68(append() is a wrapper for insert())
jQuery: jquery/jquery/blob/master/src/manipulation.js#L126-L132(domManip returns this)
Lesser known, but valid just the same:
Bonzo: ded/bonzo/blob/master/src/bonzo.js#L443-L450, this.each wraps each with returns its target object, in this case |this|, as tested here: ded/bonzo/blob/master/tests/dommanip_insertions-test.js#L103-L112
Even the grandaddy of them all:
Prototype: sstephenson/prototype/blob/master/src/prototype/dom/dom.js#L891-L946(Elemen.insert, Element.prototype.insert are wrappers for insert(), which returns the element, as tested here: sstephenson/prototype/blob/master/test/unit/dom_test.js#L148 )
Dojo agrees and returns the appended element from a call to dojo.place — but I'd argue that it's API is different from those above and it actually makes sense in the dojo API context.
The point of my response: it's not a blanket policy for all newly created APIs.
nope
el.appendChild(document.createElement("another")).parentNode.appendChild(document.createElement("another")) ... etc
the way I use it is to add and address ... once again
var current = el.firstChild || el.appenChild(document.createElement("first"));
^_^
On Wed, Dec 5, 2012 at 1:50 AM, Jussi Kalliokoski <jussi.kalliokoski at gmail.com> wrote:
My 2 cents against the windmills...
I personally think returning
this
in absence of any meaningful value (and chaining in general) is a bad pattern. Chaining leads to worse readability (there's nothing subjective about this, if you have to scan the code to another page to figure out which object the code is interacting with, it's bad readability)
This is an excellent point, and has changed my mind. I return to not supporting the "return this" for these cases. Thanks.
and returning this is more future-hostile than returning
Dart has a cascade operator (..) [1]. That's the Right Way to do chaining. We should watch how it goes in Dart before prematurely committing to this "iffy" simulation of cascading at the API level.
- Kevin
[1] www.dartlang.org/docs/spec/latest/dart-language-specification.html#h.30hsq2v14fk2
On Wed, Dec 5, 2012 at 1:04 PM, Mark S. Miller <erights at google.com> wrote:
On Wed, Dec 5, 2012 at 1:50 AM, Jussi Kalliokoski <jussi.kalliokoski at gmail.com> wrote:
My 2 cents against the windmills...
I personally think returning
this
in absence of any meaningful value (and chaining in general) is a bad pattern. Chaining leads to worse readability (there's nothing subjective about this, if you have to scan the code to another page to figure out which object the code is interacting with, it's bad readability)This is an excellent point, and has changed my mind. I return to not supporting the "return this" for these cases. Thanks.
In the cases that we identified as qualifying for the criteria that you established at the meeting, a "chain" will most likely only have one more method call or property access after .set()—any of the iterable producing methods (keys, values, entries), forEach, or the size property. But this also now makes any operation where I want to mutate the map/set and immediately do something with that object a two part exercise.
"some people will misuse it" can be the argument for anything, that doesn't make it a strong or meaningful argument.
cc'ing Doug Crockford, Erik Arvidsson, Alex Russell, Yehuda Katz to weigh in on this.
let's summarize then:
-
~Weak/Map#set and Set#add returns void 1.1 void as return is considered useless
-
cahinability looks like the most common pattern so those methods should return
this
2.1 example: return map.has(key) ? map.get(key) : map.set(key, operation()).get(key); 2.2 useful to set and send same object 2.3 chainability might lead to bad readability -
Smalltalk returns the value with #put and few here think that should be the best thing to return 3.1 example: return map.has(key) ? map.get(key) : map.set(key, operation()); // as result of operation() 3.2 above example to simulate the equivalent of this common operation: return obj.hasOwnProperty(key) ? obj[key] : (obj[key] = operation()) 3.3 useful to set without addressing values generators/creators returning them anyhow since the map reference is already known 3.4 Map and WeakMap are used as storages so always referenced while set values do not have necessarily to be addressed (boring, problematic, global pollution without strict, let vs var, common problems we all know with references)
obj.set(key, value) might reflect (obj[key] = value) semantic rather than ((obj[key] = value), obj) which is not semantic but apparently the most used pattern with all libraries out there except Dojo that agreed on Smalltalk approach.
Also HTMLElement#appendChild(newChild) where newChild is returned is more
familiar but sometimes we need to append more children so that this
might
be more appropriate return.
Last, but not least, no pattern blocks anyone to reach same goals inline:
get the value void: return map.set(key, value) || value; this: return map.set(key, value).get(key); value: return map.set(key, value);
get the instance void: return map.set(key, value) || map; this: return map.set(key, value); value: return map.set(key, value), map;
br
forgot latter comparison with an unknown value
get the value void: var v = generate(); map.set(key, v); return v; this: return map.set(key, generate()).get(key); value: return map.set(key, generate());
br
On Wed, Dec 5, 2012 at 10:04 AM, Mark S. Miller <erights at google.com> wrote:
On Wed, Dec 5, 2012 at 1:50 AM, Jussi Kalliokoski <jussi.kalliokoski at gmail.com> wrote:
My 2 cents against the windmills...
I personally think returning
this
in absence of any meaningful value (and chaining in general) is a bad pattern. Chaining leads to worse readability (there's nothing subjective about this, if you have to scan the code to another page to figure out which object the code is interacting with, it's bad readability)This is an excellent point, and has changed my mind. I return to not supporting the "return this" for these cases. Thanks.
The readability of chaining is very subjective. I find it perfectly readable.
Andrea and a few others feel strongly about this, and I respect that. But most JS libraries have gone the other way, and authors in general seem to like that direction. This constitutes pretty clear evidence about which pattern JS devs en masse prefer, and we shouldn't pretend like we know better than them. In the absence of technical problems that doom a solution, let's vote with the herd.
I think here you have more libraries authors than users, with daily basis tasks to solve without jQuery and others ... that's why here things are different from "out there", is not about knowing more or anything, IMHO.
Said that, since as I have written "no pattern blocks anyone to reach same goals inline" I don't even feel that strong about it, I am just saying what would be the ideal scenario in my opinion, wondering initially why it has been chosen a different one.
I am happy to read that I wasn't the only one preferring the value instead
of this
so ... mine was just a summary for CC'ed people and nothing else.
All I would like to believe is that these discussions are performed more often in this ML and before Meeting Notes so you have more material there before making decisions.
Br
On Wed, Dec 5, 2012 at 2:02 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
let's summarize then:
~Weak/Map#set and Set#add returns void 1.1 void as return is considered useless
cahinability looks like the most common pattern so those methods should return
this
2.1 example: return map.has(key) ? map.get(key) : map.set(key, operation()).get(key); 2.2 useful to set and send same object 2.3 chainability might lead to bad readabilitySmalltalk returns the value with #put and few here think that should be the best thing to return 3.1 example: return map.has(key) ? map.get(key) : map.set(key, operation()); // as result of operation() 3.2 above example to simulate the equivalent of this common operation: return obj.hasOwnProperty(key) ? obj[key] : (obj[key] = operation()) 3.3 useful to set without addressing values generators/creators returning them anyhow since the map reference is already known 3.4 Map and WeakMap are used as storages so always referenced while set values do not have necessarily to be addressed (boring, problematic, global pollution without strict, let vs var, common problems we all know with references)
obj.set(key, value) might reflect (obj[key] = value) semantic rather than ((obj[key] = value), obj) which is not semantic but apparently the most used pattern with all libraries out there except Dojo that agreed on Smalltalk approach.
Also HTMLElement#appendChild(newChild) where newChild is returned is more familiar but sometimes we need to append more children so that
this
might be more appropriate return.Last, but not least, no pattern blocks anyone to reach same goals inline:
get the value void: return map.set(key, value) || value; this: return map.set(key, value).get(key); value: return map.set(key, value);
get the instance void: return map.set(key, value) || map; this: return map.set(key, value); value: return map.set(key, value), map;
void and value prevent all of the following:
Add value to the Set and get a fresh iterable for the keys, values, entries:
set.add( value ).keys(); set.add( value ).values(); set.add( value ).entries();
Add value to the Set and send each value in the set to another operation:
set.add( value ).forEach( item => ...send to some operation.... );
Add value to the Set and spread into an array of unique items:
[ ...set.add(value) ]; // [ v, v, v, ... ]
...and same for Map
While I completely agree that there is much to learn from other successful languages and the patterns that they were designed on, I also believe whole-heartedly in Allen Wirfs-Brock's prediction of JavaScript as the dominant programming language in the coming decades[0]. In this belief, I feel very strongly that JavaScript is in a position to evolve itself from within—yes, Smalltalk returned the value, but this doesn't mean that JavaScript should ignore the patterns that emerged in its own ecosystem, developed around its own capabilities for the sake of appeasing Smalltalk.
I'd argue that most people who write JavaScript and receive a paycheck for doing so, have never written Smalltalk and most never will. (This statement is in no way a judgement of Smalltalk)
Rick
Readability or library preference aside, I still think it's bizarre that
map.set(key, val)
is analogous to
(dict[key] = val, dict)
and not to
dict[key] = val
When I'm using a fluent library like jQuery or a configuration DSL like those in the npm packages surveyed, I can see the attraction of chaining. But when I am using a basic primitive of the language, I expect uniformity across primitives.
On Wed, Dec 5, 2012 at 3:13 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
Said that, since as I have written "no pattern blocks anyone to reach same goals inline" I don't even feel that strong about it, I am just saying what would be the ideal scenario in my opinion, wondering initially why it has been chosen a different one.
I am happy to read that I wasn't the only one preferring the value instead of
this
so ... mine was just a summary for CC'ed people and nothing else.All I would like to believe is that these discussions are performed more often in this ML and before Meeting Notes so you have more material there before making decisions.
This is at least the third time that you've implied that "the committee" made a decision without considering the facts. I chose to not respond previously, but I'm going to tell you right now that I reject your implication—on the grounds that you're wrong. I spent a lot of time preparing and researching patterns of "post mutation returns" in widely adopted codebases, as well as measuring developer expectations by way of informal survey. If I had any reason to believe that this was not a desirable approach, I would've requested removal from agenda myself. Prior to the meeting, I spoke in person with several different committee members to get a general feel and the support was unanimously in favor.
On Wed, Dec 5, 2012 at 3:26 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
Readability or library preference aside, I still think it's bizarre that
map.set(key, val)
is analogous to
(dict[key] = val, dict)
and not to
dict[key] = val
When I'm using a fluent library like jQuery or a configuration DSL like those in the npm packages surveyed, I can see the attraction of chaining. But when I am using a basic primitive of the language, I expect uniformity across primitives.
This argument won't hold when the language doesn't make any such "uniformity" promises, eg.
array.push(val); // new length array[ array.length - 1 ] = val; // val
I never said they made a decision without valid considerations, I am saying I haven't seen any informal survey here, the place where theoretically we discuss ECMAScript and its present and future.
I want believe it makes sense to keep discussing here, as it has always been, and I don't want to be informed after about decisions made somewhere else ... even if ultra valid, a quick note/survey here cannot hurt but inform subscribers, I hope you agree.
On Wed, Dec 5, 2012 at 3:55 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
I never said they made a decision without valid considerations, I am saying I haven't seen any informal survey here, the place where theoretically we discuss ECMAScript and its present and future.
I've already explained why that is: there is a general aversion to "design by survey". I only used the information in the survey to help me decide if it was even worth using committee time to discuss.
I want believe it makes sense to keep discussing here, as it has always been, and I don't want to be informed after about decisions made somewhere else ... even if ultra valid, a quick note/survey here cannot hurt but inform subscribers, I hope you agree.
Sure, I mistakenly assumed that this had been cc'ed to es-discuss lists.w3.org/Archives/Public/public-script-coord/2012OctDec/0164.html ...and never bothered to double check.
It does not matter what Map.prototype.set returns. Almost all call sites will ignore the return value, just like they do in every other language.
If (like me) you have strong feelings about this that are not backed by much more than personal taste, I suggest a brisk walk. That's what I'll be doing.
Respectfully yours,
On 12/5/12 at 1:50 AM, jussi.kalliokoski at gmail.com (Jussi Kalliokoski) wrote:
I personally think returning
this
in absence of any meaningful value (and chaining in general) is a bad pattern.
</lurk>
I have to agree with Jussi here. Whenever I consider chaining using the returned values from the various things called, my programming paranoia hair stands on end. Let me try to explain:
Whenever I program, I try to trust as little code as possible. With chaining, there are two possibilities for getting the wrong answer in the returned value:
- I or someone else wrote it, but screwed up,
- Someone hostile wrote it and is trying to trip me up.
If there is a language construct that allows chaining -- like the Pascal "with" construct -- then I am only trusting the language*, not other fragments of programs. If I depend on things I call returning the correct "this", then I am depending on them and my dependency set is a lot larger. A larger dependency set makes me nervous.
Cheers - Bill
- For the really paranoid, minimizing the parts of the language depended on is important. Not all JS implementations behave the same way in the corner cases.
<lurk>
Bill Frantz |Security, like correctness, is| Periwinkle (408)356-8506 |not an add-on feature. - Attr-| 16345 Englewood Ave www.pwpconsult.com |ibuted to Andrew Tanenbaum | Los Gatos, CA 95032
On Wed, Dec 5, 2012 at 10:33 PM, Bill Frantz <frantz at pwpconsult.com> wrote:
On 12/5/12 at 1:50 AM, jussi.kalliokoski at gmail.com (Jussi Kalliokoski) wrote:
I personally think returning
this
in absence of any meaningful value(and chaining in general) is a bad pattern.
</lurk>
I have to agree with Jussi here. Whenever I consider chaining using the returned values from the various things called, my programming paranoia hair stands on end. Let me try to explain:
Whenever I program, I try to trust as little code as possible. With chaining, there are two possibilities for getting the wrong answer in the returned value:
- I or someone else wrote it, but screwed up,
- Someone hostile wrote it and is trying to trip me up.
If there is a language construct that allows chaining -- like the Pascal "with" construct -- then I am only trusting the language*, not other fragments of programs. If I depend on things I call returning the correct "this", then I am depending on them and my dependency set is a lot larger. A larger dependency set makes me nervous.
Cheers - Bill
- For the really paranoid, minimizing the parts of the language depended on is important. Not all JS implementations behave the same way in the corner cases.
Again, I reject the notion that "someone might screw up" is a valid argument for this, or any, discussion. It's one thing to be aware of the potential for misuse, but entirely another to succumb to "fear driven design".
On Wed, Dec 5, 2012 at 10:43 PM, Rick Waldron <waldron.rick at gmail.com>wrote:
On Wed, Dec 5, 2012 at 3:26 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
Readability or library preference aside, I still think it's bizarre that
map.set(key, val)
is analogous to
(dict[key] = val, dict)
and not to
dict[key] = val
When I'm using a fluent library like jQuery or a configuration DSL like those in the npm packages surveyed, I can see the attraction of chaining. But when I am using a basic primitive of the language, I expect uniformity across primitives.
This argument won't hold when the language doesn't make any such "uniformity" promises, eg.
array.push(val); // new length array[ array.length - 1 ] = val; // val
That's just a bad analogy, because that's not what push does, since it has a variadic argument (admittedly, I don't think returning the length is useful anyway, but if it returned the value, should it return the first, last, or all of them?). And if it comes down to precedents in the language, even Array#forEach() returns undefined, contrary to popular libraries out there. Let's keep some consistency here.
I agree with you, fear-driven design is bad. But don't you agree that if
there's chaining, it's better done at language level rather than having all
APIs be polluted by this
returns? After all, the APIs can't guarantee a
this
return, since they might have something actually meaningful to
return, otherwise we might as well just replace undefined
with this
as
the default return value.
We could introduce mutable primitives so that meaningful return values
could be stored in arguments, kinda like in C, but instead of error values,
we'd be returning this
, heheheh. :)
I'm curious, do you have any code examples of maps/sets that could be made clearer by chaining?
On 6 December 2012 05:05, Rick Waldron <waldron.rick at gmail.com> wrote:
Again, I reject the notion that "someone might screw up" is a valid argument for this, or any, discussion. It's one thing to be aware of the potential for misuse, but entirely another to succumb to "fear driven design".
"Fear driven design" is pejorative. The argument really is about the ability to do local reasoning as much as possible, which is a very valid concern, especially when reading somebody else's code using somebody else's library.
I agree with other voices in this thread that in general, returning 'this' rather is an anti pattern. You can get away with it if you limit it to very few well-known library functions, but I doubt that blessing such style in the std lib does help that cause.
On Wed, Dec 5, 2012 at 8:05 PM, Rick Waldron <waldron.rick at gmail.com> wrote:
On Wed, Dec 5, 2012 at 10:33 PM, Bill Frantz <frantz at pwpconsult.com> wrote:
On 12/5/12 at 1:50 AM, jussi.kalliokoski at gmail.com (Jussi Kalliokoski) wrote:
I personally think returning
this
in absence of any meaningful value (and chaining in general) is a bad pattern.</lurk>
I have to agree with Jussi here. Whenever I consider chaining using the returned values from the various things called, my programming paranoia hair stands on end. Let me try to explain:
Whenever I program, I try to trust as little code as possible. With chaining, there are two possibilities for getting the wrong answer in the returned value:
- I or someone else wrote it, but screwed up,
- Someone hostile wrote it and is trying to trip me up.
If there is a language construct that allows chaining -- like the Pascal "with" construct -- then I am only trusting the language*, not other fragments of programs. If I depend on things I call returning the correct "this", then I am depending on them and my dependency set is a lot larger. A larger dependency set makes me nervous.
Cheers - Bill
- For the really paranoid, minimizing the parts of the language depended on is important. Not all JS implementations behave the same way in the corner cases.
Again, I reject the notion that "someone might screw up" is a valid argument for this, or any, discussion. It's one thing to be aware of the potential for misuse, but entirely another to succumb to "fear driven design".
Is "fear driven design" just a derogatory phrase for "defensive programming"?
On Thu, Dec 6, 2012 at 3:48 AM, Jussi Kalliokoski < jussi.kalliokoski at gmail.com> wrote:
And if it comes down to precedents in the language, even Array#forEach() returns undefined, contrary to popular libraries out there. Let's keep some consistency here.
Array.prototype.map and Array.prototype.filter return newly created arrays and as such, are chainable (and will have the same benefits as I described above)
// map and return a fresh iterable of values array.map( v => ... ).values()
// map and return a fresh iterable of entries (index/value pairs) array.filter( v => ... ).entries()
I agree with you, fear-driven design is bad. But don't you agree that if there's chaining, it's better done at language level rather than having all APIs be polluted by
this
returns?
Who said all APIs would return this
? We specified a clear criteria.
After all, the APIs can't guarantee a
this
return,
Yes they can, they return what the specification defines them to return.
since they might have something actually meaningful to return, otherwise we might as well just replace
undefined
withthis
as the default return value.
In the cases I presented, I believe that returning this
IS the meaningful
return.
We could introduce mutable primitives so that meaningful return values could be stored in arguments, kinda like in C, but instead of error values, we'd be returning
this
, heheheh. :)I'm curious, do you have any code examples of maps/sets that could be made clearer by chaining?
This is incredibly frustrating and indicates to me that you're not actually reading this thread, but still find it acceptable to contribute to the discussion.
On Thu, Dec 6, 2012 at 6:53 AM, Andreas Rossberg <rossberg at google.com>wrote:
On 6 December 2012 05:05, Rick Waldron <waldron.rick at gmail.com> wrote:
Again, I reject the notion that "someone might screw up" is a valid argument for this, or any, discussion. It's one thing to be aware of the potential for misuse, but entirely another to succumb to "fear driven design".
"Fear driven design" is pejorative.
I'll own that, my apologies.
The argument really is about the ability to do local reasoning as much as possible, which is a very valid concern, especially when reading somebody else's code using somebody else's library.
I agree with other voices in this thread that in general, returning 'this' rather is an anti pattern.
The evidence I've brought to this discussion shows that the most widely used and depended upon libraries heavily favor the pattern.
You can get away with it if you limit it to very few well-known library functions,
We established a criteria for built-ins, I don't think we should be using "return this" by default, but indeed we should where it will be an expressive benefit.
On Thu, Dec 6, 2012 at 10:34 AM, Mark S. Miller <erights at google.com> wrote:
Is "fear driven design" just a derogatory phrase for "defensive programming"?
Not at all.
I wrote "fear driven design" in a moment of frustration, referring only to the opposition of adopting a (widely held as) best practice based solely on what appears to be subjective opinions versus objective observation of real world use cases.
The argument:
"if you have to scan the code to another page to figure out which object the code is interacting with, it's bad readability"
Would apply in only one extreme case that is so incredibly stupid, I posit this will be an anomaly:
set.add(...).add(...).add(...).add(...).add(...).add(...).add(...)...
The likely very common cases are quite nice and not at all confusing to read:
(copied from an earlier message in this thread)
Add value to the Set and get a fresh iterable for the keys, values, entries:
set.add( value ).keys(); set.add( value ).values(); set.add( value ).entries();
Add value to the Set and send each value in the set to another operation:
set.add( value ).forEach( item => ...send to some operation.... );
Add value to the Set and spread into an array of unique items:
[ ...set.add(value) ]; // [ a, b, c, ... ]
And of course, the same for Map.
More here: gist.github.com/4219024
I have respect for valid technical and security related concerns and would certainly love to discuss those if any have been identified.
On Thu, Dec 6, 2012 at 7:32 PM, Rick Waldron <waldron.rick at gmail.com> wrote:
Array.prototype.map and Array.prototype.filter return newly created arrays and as such, are chainable (and will have the same benefits as I described above)
// map and return a fresh iterable of values array.map( v => ... ).values()
// map and return a fresh iterable of entries (index/value pairs) array.filter( v => ... ).entries()
Of course, but that's pears and apples, .set() doesn't create a new instance. And btw, that .values() is redundant.
I agree with you, fear-driven design is bad. But don't you agree that if there's chaining, it's better done at language level rather than having all APIs be polluted by
this
returns?Who said all APIs would return
this
? We specified a clear criteria.
You're dodging my question: isn't it better for the chaining to be supported by the language semantics rather than be injected to APIs in order to have support?
After all, the APIs can't guarantee a
this
return,Yes they can, they return what the specification defines them to return.
What I mean is that the not all functions in an API can return this
anyway (like getters), so it's inconsistent. After all, it's not a very
useful API if you can just set but not get.
since they might have something actually meaningful to return, otherwise we
might as well just replace
undefined
withthis
as the default return value.In the cases I presented, I believe that returning
this
IS the meaningful return.
No, it's a generic return value if it's applied to everything that's not a getter.
We could introduce mutable primitives so that meaningful return values could be stored in arguments, kinda like in C, but instead of error values, we'd be returning
this
, heheheh. :)I'm curious, do you have any code examples of maps/sets that could be made clearer by chaining?
This is incredibly frustrating and indicates to me that you're not actually reading this thread, but still find it acceptable to contribute to the discussion.
I'm sorry you feel that way, but calm down. I've read the gist all right and just read the latest version, and imho it's quite a biased example, you're making it seem harder than it actually is. For example, the last paragraph:
( map.set(key, value), set ).keys();
// simpler: map.set(key, value); map.keys();
( set.add(value), set ).values();
// simpler: set.add(value); set.values;
( set.add(value), set ).forEach( val => .... );
// simpler: set.add(value); set.forEach( val => .... );
Why would you need to stuff everything in one line? This way it's more version control friendly as well, since those two lines of code have actually nothing to do with each other, aside from sharing dealing with the same object. Why do you want to get all of those things from .set()/.add(), methods which have nothing to do with what you're getting at?
On Thu, Dec 6, 2012 at 8:25 PM, Jussi Kalliokoski < jussi.kalliokoski at gmail.com> wrote:
On Thu, Dec 6, 2012 at 7:32 PM, Rick Waldron <waldron.rick at gmail.com>wrote:
Array.prototype.map and Array.prototype.filter return newly created arrays and as such, are chainable (and will have the same benefits as I described above)
// map and return a fresh iterable of values array.map( v => ... ).values()
// map and return a fresh iterable of entries (index/value pairs) array.filter( v => ... ).entries()
Of course, but that's pears and apples, .set() doesn't create a new instance. And btw, that .values() is redundant.
Wait, sorry about that, wrote before I investigated.
On Thu, Dec 6, 2012 at 1:25 PM, Jussi Kalliokoski < jussi.kalliokoski at gmail.com> wrote:
On Thu, Dec 6, 2012 at 7:32 PM, Rick Waldron <waldron.rick at gmail.com>wrote:
Array.prototype.map and Array.prototype.filter return newly created arrays and as such, are chainable (and will have the same benefits as I described above)
// map and return a fresh iterable of values array.map( v => ... ).values()
// map and return a fresh iterable of entries (index/value pairs) array.filter( v => ... ).entries()
Of course, but that's pears and apples, .set() doesn't create a new instance. And btw, that .values() is redundant.
values() returns an iterable of the values in the array. Array, Map and Set will receive all three: keys(), values(), entries(). Feel free to start a new thread if you want to argue about iterator protocol.
I agree with you, fear-driven design is bad. But don't you agree that if there's chaining, it's better done at language level rather than having all APIs be polluted by
this
returns?Who said all APIs would return
this
? We specified a clear criteria.You're dodging my question: isn't it better for the chaining to be supported by the language semantics rather than be injected to APIs in order to have support?
I'm absolutely not dodging the question, I answered this in a previous message, much earlier. Cascade/monocle/mustache is not a replacement here.
After all, the APIs can't guarantee a
this
return,Yes they can, they return what the specification defines them to return.
What I mean is that the not all functions in an API can return
this
anyway (like getters), so it's inconsistent. After all, it's not a very useful API if you can just set but not get.
That's exactly my point. The set/add API return this, allowing post-mutation operations to be called: such as get or any of the examples I've given throughout this thread.
since they might have something actually meaningful to return, otherwise
we might as well just replace
undefined
withthis
as the default return value.In the cases I presented, I believe that returning
this
IS the meaningful return.No, it's a generic return value if it's applied to everything that's not a getter.
No one said anything about applying return this to "everything that's not a getter". That was exactly what the criteria we have consensus on defines. It's in the meeting notes for Nov. 29.
Please read everything I've written so far, it's not fair to make me constantly repeat myself in this thread.
We could introduce mutable primitives so that meaningful return values could be stored in arguments, kinda like in C, but instead of error values, we'd be returning
this
, heheheh. :)I'm curious, do you have any code examples of maps/sets that could be made clearer by chaining?
This is incredibly frustrating and indicates to me that you're not actually reading this thread, but still find it acceptable to contribute to the discussion.
I'm sorry you feel that way, but calm down. I've read the gist all right and just read the latest version, and imho it's quite a biased example, you're making it seem harder than it actually is. For example, the last paragraph:
( map.set(key, value), set ).keys();
// simpler: map.set(key, value); map.keys();
( set.add(value), set ).values();
// simpler: set.add(value); set.values;
( set.add(value), set ).forEach( val => .... );
// simpler: set.add(value); set.forEach( val => .... );
Of course I could've shown it as you have here, but I made examples where the intention was to match the preceding examples illustrated in the gist.
Why would you need to stuff everything in one line?
As evidenced several times throughout this thread, the pattern is widely implemented in the most commonly used library APIs, so I guess the answer is "The kids love it".
This way it's more version control friendly as well, since those two lines of code have actually nothing to do with each other, aside from sharing dealing with the same object. Why do you want to get all of those things from .set()/.add(), methods which have nothing to do with what you're getting at?
You could just as easily have them on separate lines, but in cases where it might be desirable to immediately operate on the result of the mutation, chaining the next method call has the net appearance of a single tasks (if that's how a programmer so chooses to express their program).
On Thu, Dec 6, 2012 at 8:44 PM, Rick Waldron <waldron.rick at gmail.com> wrote:
values() returns an iterable of the values in the array. Array, Map and Set will receive all three: keys(), values(), entries(). Feel free to start a new thread if you want to argue about iterator protocol.
Yes, I apologized for that mistake already, I remembered incorrectly. I don't have a want to argue, just like I'm sure you don't.
I'm absolutely not dodging the question, I answered this in a previous
message, much earlier. Cascade/monocle/mustache is not a replacement here.
That wasn't the question I asked. Cascade/monocle/mustache aren't even ready yet, and are hence in no way an indication that chaining cannot be made a language-side construct. I believe it can and will, and at that point, returning this becomes completely meaningless. But (I don't see) how can you fix this on the language syntax side:
var obj = { foo: bar, baz: taz } set.add(obj) return set
instead of simply:
return set.add({ foo: bar, baz: taz })
What I mean is that the not all functions in an API can return
this
anyway (like getters), so it's inconsistent. After all, it's not a very useful API if you can just set but not get.That's exactly my point. The set/add API return this, allowing post-mutation operations to be called: such as get or any of the examples I've given throughout this thread.
What? I'm really sorry, but I can't understand how what I said leads to your point. But I bet we're both wasting our time with this part, so it's probably best to just leave it.
No one said anything about applying return this to "everything that's not a getter". That was exactly what the criteria we have consensus on defines. It's in the meeting notes for Nov. 29.
Sorry, about that, the meeting notes (in the part "Cascading this returns") just say:
"Supporting agreement" "(Discussion to determine a criteria for making this API specification distinction)" "Consensus... with the criteria that these methods are not simply a set of uncoordinated side effects that happen to have a receiver in common, but a set of coordinated side effects on a specific receiver and providing access to the target object post-mutation."
With no reference to the logic behind the conclusion ("these methods are not simply a set of uncoordinated side effects that happen to have a receiver in common"). I fail to see how .set()/.add() are a special case. Am I missing something?
Please read everything I've written so far, it's not fair to make me
constantly repeat myself in this thread.
I agree, and I'm sorry, but I have, at least everything on this thread, those referred to and those that have seemed related. I'm doing my best, but I'm afraid I can't keep up with every thread in my inbox, and I don't think it's a good reason for me not to contribute at all.
Of course I could've shown it as you have here, but I made examples where
the intention was to match the preceding examples illustrated in the gist.
Fair enough, but I fail to see the convenience in your examples.
Why would you need to stuff everything in one line?
As evidenced several times throughout this thread, the pattern is widely implemented in the most commonly used library APIs, so I guess the answer is "The kids love it".
document.write() is widely implemented too, doesn't make it good or worth repeating.
This way it's more version control friendly as well, since those two lines
of code have actually nothing to do with each other, aside from sharing dealing with the same object. Why do you want to get all of those things from .set()/.add(), methods which have nothing to do with what you're getting at?
You could just as easily have them on separate lines, but in cases where it might be desirable to immediately operate on the result of the mutation, chaining the next method call has the net appearance of a single tasks (if that's how a programmer so chooses to express their program).
So it's taste, rather than convenience?
On Thu, Dec 6, 2012 at 2:41 PM, Jussi Kalliokoski < jussi.kalliokoski at gmail.com> wrote:
On Thu, Dec 6, 2012 at 8:44 PM, Rick Waldron <waldron.rick at gmail.com>wrote:
values() returns an iterable of the values in the array. Array, Map and Set will receive all three: keys(), values(), entries(). Feel free to start a new thread if you want to argue about iterator protocol.
Yes, I apologized for that mistake already, I remembered incorrectly. I don't have a want to argue, just like I'm sure you don't.
All this misses your important "pears and oranges" point. These are not
mutable APIs, which is a key distinction. The sort method would have been a
good example of a mutable API returning this
. But it's not exactly a
model to emulate.
I'm absolutely not dodging the question, I answered this in a previous
message, much earlier. Cascade/monocle/mustache is not a replacement here.
That wasn't the question I asked. Cascade/monocle/mustache aren't even ready yet, and are hence in no way an indication that chaining cannot be made a language-side construct. I believe it can and will, and at that point, returning this becomes completely meaningless. But (I don't see) how can you fix this on the language syntax side:
var obj = { foo: bar, baz: taz } set.add(obj) return set
instead of simply:
return set.add({ foo: bar, baz: taz })
What I mean is that the not all functions in an API can return
this
anyway (like getters), so it's inconsistent. After all, it's not a very useful API if you can just set but not get.That's exactly my point. The set/add API return this, allowing post-mutation operations to be called: such as get or any of the examples I've given throughout this thread.
What? I'm really sorry, but I can't understand how what I said leads to your point. But I bet we're both wasting our time with this part, so it's probably best to just leave it.
No one said anything about applying return this to "everything that's not a getter". That was exactly what the criteria we have consensus on defines. It's in the meeting notes for Nov. 29.
Sorry, about that, the meeting notes (in the part "Cascading this returns") just say:
"Supporting agreement" "(Discussion to determine a criteria for making this API specification distinction)" "Consensus... with the criteria that these methods are not simply a set of uncoordinated side effects that happen to have a receiver in common, but a set of coordinated side effects on a specific receiver and providing access to the target object post-mutation."
With no reference to the logic behind the conclusion ("these methods are not simply a set of uncoordinated side effects that happen to have a receiver in common"). I fail to see how .set()/.add() are a special case. Am I missing something?
Please read everything I've written so far, it's not fair to make me
constantly repeat myself in this thread.
I agree, and I'm sorry, but I have, at least everything on this thread, those referred to and those that have seemed related. I'm doing my best, but I'm afraid I can't keep up with every thread in my inbox, and I don't think it's a good reason for me not to contribute at all.
Of course I could've shown it as you have here, but I made examples where
the intention was to match the preceding examples illustrated in the gist.
Fair enough, but I fail to see the convenience in your examples.
Why would you need to stuff everything in one line?
As evidenced several times throughout this thread, the pattern is widely implemented in the most commonly used library APIs, so I guess the answer is "The kids love it".
But which kids? There certainly appears to be quite a sampling bias in your survey -- I didn't see a single actual collection library. Sampling their choices would be the most helpful, not what the kids are doing.
Plus there are other alternatives I haven't seen discussed, so the design
space has barely been explored. For instance buckets [1] is a nice example
of a collection library that takes an approach more reminiscent of
javascript's existing array mutation methods -- its add method returns
true
if the item was newly created or false
if it was already present
in the collection -- a lot like javascript's delete operator. I'm not
necessarily advocating for this, just offering up the idea that any survey
should look closer at existing collection libraries to get a better feel
for the full design space.
document.write() is widely implemented too, doesn't make it good or worth repeating.
That's a low blow :)
This way it's more version control friendly as well, since those two
lines of code have actually nothing to do with each other, aside from sharing dealing with the same object. Why do you want to get all of those things from .set()/.add(), methods which have nothing to do with what you're getting at?
You could just as easily have them on separate lines, but in cases where it might be desirable to immediately operate on the result of the mutation, chaining the next method call has the net appearance of a single tasks (if that's how a programmer so chooses to express their program).
So it's taste, rather than convenience?
Is there really a difference?
Personally I believe only immutable APIs should ever return this
, since
there's real value there. Mutable APIs should punish you, even if only
slightly. But that's just my taste.
Had same thoughts on returning true or false as map.delete(key) would do.
However, it's easy to have ambiguity there ... assuming the key can always be set, 'cause even a frozen Map should be able, and it is, to set a key internally, will true mean that key was not there ? will false mean that key was already set or we reached maximum number of keys? Shouldn't latter case be an error as it is for Array(Math.pow(2, 32) - 1).push("invalid array length"); ?
This boolean return is semantic with what delete does, less semantic with (setting[property] = value) logic thought but surely a valid possibility.
Anyway, I would like to know what other TC39 members think, cause they all agreed already and i see this thread too dead and philosophical at this point ... :-/
We all have made our points but it has been decided then ... well, we should simply deal with it?
br
Andrea Giammarchi wrote:
Anyway, I would like to know what other TC39 members think, cause they all agreed already
You've heard from Mark and me (also Jason Orendorff of Mozilla, who is de-facto champion of some ES6 proposals) in this thread, along with Rick. You won't get a definitive resolution on es-discuss from TC39, however.
Allen may have a thought on how to proceed, but I think this discussion is still valuable so long as we don't rehash or butt heads too much.
On 6 December 2012 18:38, Rick Waldron <waldron.rick at gmail.com> wrote:
I agree with other voices in this thread that in general, returning 'this' rather is an anti pattern.
The evidence I've brought to this discussion shows that the most widely used and depended upon libraries heavily favor the pattern.
That's not necessarily a contradiction. ;)
Andreas Rossberg wrote:
On 6 December 2012 18:38, Rick Waldron<waldron.rick at gmail.com> wrote:
I agree with other voices in this thread that in general, returning 'this' rather is an anti pattern. The evidence I've brought to this discussion shows that the most widely used and depended upon libraries heavily favor the pattern.
That's not necessarily a contradiction. ;)
That might seem a little bit mean. Let me help :-).
If we had "fluent syntax", say Dave's blog.mozilla.org/dherman/2011/12/01/now-thats-a-nice-stache variant, all along, would |this|-return have been developed as a pattern? Hard to know but the odds arguably go down. They don't go up.
A cascade syntax would help avoid the pigeon-hole problem face by setters: RHS value, like assignment? |this| for chaining? If that could be an unnecessary dilemma, we could spend our time on other things.
But we don't have cascade syntax in ES6. We are adding Map/Set/WeakMap. So back to the dilemma.
If one thing this is clear from this discussion, it is that different programmers have different preferences (perhaps even changeable depending on use case). So, no single standard API will suit everyone and the language support for different patterns could be improved.
Meanwhile, it seems one can get both via proxies (wrapping selected target methods to return this or argument). I had some trouble finding a direct proxy implementation in released engines, so I'm using Tom's harmony-reflect shim in node). See code below, which outputs:
$ node --harmony proxy-chain.js
undefined { xs: [ 5 ] }
{ xs: [ 5, 6 ] }
{ xs: [ 5, 6, 7, 8 ] }
9 { xs: [ 5, 6, 7, 8, 9 ] }
The original collection's add method returns undefined (line 1), the this-chained proxy's add returns this (lines 2,3), the value-chained proxy's add returns the added value (line 4).
Claus
// install npm install harmony-reflect // run: node --harmony proxy-chain.js
var Reflect = require('harmony-reflect'); // also shims direct proxies
// enable chainable this-return for methods in target function chain_this(target,methods) { return Proxy(target ,{get:function(target,name,receiver){ return methods.indexOf(name)!==-1 ? function(){ target[name].apply(target,arguments); return receiver } : target[name] } }); }
// enable chainable value-return for unary! methods in target function chain_value(target,methods) { return Proxy(target ,{get:function(target,name,receiver){ return methods.indexOf(name)!==-1 ? function(arg){ targetname; return arg } : target[name] } }); }
function X() { this.xs = []; } X.prototype.add = function(x){ this.xs.push(x) }; // returns void
var x = new X();
console.log( x.add(5) , x );
var xt = chain_this(x,['add']);
console.log( xt.add(6) );
console.log( xt.add(7).add(8) );
var xv = chain_value(x,['add']);
console.log( xv.add(9) , xv );
What JS needs is a cascade operator. With this operator, property accessors and methods can be effectively chained without requiring methods to return this
.
I wonder what was the use case that convinced TC39 to return
this
with these methods.Accordingly, this will never work: var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery'));
And it will be something like: var query = map.has('queried') ? map.get('queried') : map.set('queried', $('myquery')).get('queried');
which is ugly and I don't really understand where map.set(k0, v0).set(k1, v1).set(k2, v2) could be useful.
Thanks for clarifications ( a use case would be already good )
br