Skip to content
This repository

forEach should chain #142

Closed
grignaak opened this Issue March 07, 2011 · 16 comments

10 participants

grignaak Jeremy Ashkenas Adam Krebs Vladimir Kovalskiy Michael Ficarra Pier Paolo Ramon Agelos Pikoulas John-David Dalton Barney Carroll brad dunbar
grignaak

To support chaining, each should return the object
_([1, 2, 3]).chain()
.each(console.log)
.map(function (a) {return a*a; })
.value()

The current workaround is to use tap:
_([1, 2, 3]).chain()
.tap(function (all) { _.each(all, console.log); })
.map(function (a) {return a*a; })
.value()

Jeremy Ashkenas
Owner

Nope, forEach should mirror the ECMAScript 5 signature (as should every other Underscore function, where possible) ... not have a special return value.

Jeremy Ashkenas jashkenas closed this March 20, 2011
Vladimir Kovalskiy

Spend 2 hours time figuring why does chain functioning incorrectly.. This info should be placed in docs about .each or .chain methods.

Michael Ficarra
Collaborator

note: I was confusing _.each and _.map here.

@vkovalskiy: I think the _.chain documentation is pretty explicit:

Returns a wrapped object. Calling methods on this object will continue to return wrapped objects until value is used.

It should be obvious then that _.tap is useful for.. exactly what it says:

Invokes interceptor with the object, and then returns object. The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.

@grignaak was trying to side-effect with console.log and produce the same object again, rather than obeying the behaviour of _.each (a map operation) and collecting the results in a new list. That's exactly what _.tap is for. Nobody should be expecting that behaviour by default.

edit: additions

Pier Paolo Ramon
yuchi commented April 05, 2012
_.mixin({
  chainedEach: function ( o, i, c ) {
    _.each( o, i, c );
    return o;
  }
});
grignaak
Vladimir Kovalskiy

@michaelficarra Could you tell me why nobody should expect this behavior by default (giving they did not learn ECMA5 spec by heart)? I found it rather strange that each does not do default chaining.

At least I propose to add info to each description saing about inability to chain by default. This could save time to somebody.

Michael Ficarra
Collaborator

note: I was confusing _.each and _.map here.

@vkovalskiy: _.each collects the return values of the provided function, building and returning a new list. _.chain is specified (and documented!) in such a way that the result of the chained method call is preserved for the next chained call. That's the whole idea behind _.chain. When you map a function that returns an undefined value for any input, you should expect the next function to operate on a list of undefined values. There's two fixes:

  1. The original solution, using _.tap to ignore the return value of _.each:

    _([1, 2, 3]).chain()
    .tap(function(all){ _.each(all, console.log.bind(console)); })
    .map(function(a){ return a * a; })
    .value()
    
  2. Return the input manually:

    _([1, 2, 3]).chain()
    .each(function(i){ console.log.apply(console, arguments); return i; })
    .map(function(a){ return a * a; })
    .value()
    
Vladimir Kovalskiy

@michaelficarra : thanks for explanation. I've found your second solution with returning results explicitly to be very convinient. Although I do not understand the phrase:

_.each collects the return values of the provided function, building and returning a new list.

As it was stated above that forEach does not intended to return anything.

Michael Ficarra
Collaborator

note: I was confusing _.each and _.map here.

@vkovalskiy:

As it was stated above that forEach does not intended to return anything.

Nope. each/forEach does produce a list of the return values. See the documentation.

Jeremy Ashkenas
Owner

Nope. each/forEach does produce a list of the return values. See the documentation.

Huh? _.each is for side effects, and returns undefined.

Michael Ficarra
Collaborator

Whoops, you're right. Very sorry, I don't know what I was thinking.

Agelos Pikoulas

I just lost a worthwhile on this, before I read this 'issue'.

I am surprised that the creator of coffeescript, where everything is comprehensible and returned aggressively (item for item in items), which make the language rock among others, doesn't want _.each to return something... Maybe so, at least it should go to the documentation cause its not obvious at all....

John-David Dalton

@anodynos I've implemented this via _.each, _.forEach. Lemme know if that works for ya.

Barney Carroll

This is the third time I've googled this unexpected behaviour. It's telling that the only contributions attempting to rationalise the existing behaviour have gotten it wrong.

brad dunbar
Collaborator

@barneycarroll _.each supports chaining in the latest release (1.6.0). :)

Barney Carroll

@braddunbar Excellent! Serves me right for not updating. Remove the wontfix tag? This thread googles pretty highly (more so than release notes!) when searching for 'underscore forEach return value' and this is the first mention that the feature request has been implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.