Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

forEach should chain #142

Closed
grignaak opened this Issue · 16 comments

10 participants

@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()

@jashkenas
Owner

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

@jashkenas jashkenas closed this
@vkovalskiy

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

@michaelficarra
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

@yuchi
_.mixin({
  chainedEach: function ( o, i, c ) {
    _.each( o, i, c );
    return o;
  }
});
@grignaak
@vkovalskiy

@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.

@michaelficarra
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()
    
@vkovalskiy

@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.

@michaelficarra
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.

@jashkenas
Owner

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

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

@michaelficarra
Collaborator

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

@anodynos

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....

@jdalton

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

@barneycarroll

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.

@braddunbar
Collaborator

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

@barneycarroll

@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.

@akre54 akre54 removed the wontfix label
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.