-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add _.transform method #1901
Add _.transform method #1901
Conversation
👍 to the addition, I'll take a peek at the implementation later tonight |
To be clear — one would want to use this over |
@jashkenas, my personal favourite example is for deep mapping that respects both // From **http://stackoverflow.com/a/25334280/1517919**
function deepMap(obj, iterator, context) {
return _.transform(obj, function(result, val, key) {
result[key] = _.isObject(val) /*&& !_.isDate(val)*/ ?
deepMap(val, iterator, context) :
iterator.call(context, val, key, obj);
});
} |
I dig it because of the extra sugar around accumulators; inferring a default accumulator based on the kind of value iterated over and the ability to exit early once a result is reduced instead of having to do something like That said, I have done some functional-fu like |
I do think this would make the |
It's being discussed on #1545. |
@jdalton Cool. If that change is made, I think it definitely makes the |
// **Transform** is an alternative to reduce that transforms `obj` to a new | ||
// `accumulator` object. | ||
_.transform = function(obj, iteratee, accumulator, context) { | ||
iteratee = optimizeCb(iteratee, context, 4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (obj == null) return/throw/???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
I think this kind of goes hand in hand with #1734 |
LGTM big +1 |
// **Transform** is an alternative to reduce that transforms `obj` to a new | ||
// `accumulator` object. | ||
_.transform = function(obj, iteratee, accumulator, context) { | ||
if (obj == null) return accumulator == null ? accumulator : {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a test as well? Right now, it's returning accumulator
if it == null
, and an empty object if it does not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely, I was trying to keep the same behavior that it'd have if you let the function run through, which is what the lodash implementation does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue being _.transform(null) === undefined
and _.transform(null, function() {}, null) === null
, where lodash returns a blank object. I think you're ternary is reversed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, derp. Fixed and added tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, I wasn't clear with my initial comment.
This may be a good time to look at implementing |
@jdalton I would definitely like to see a |
6e34888
to
6aaa958
Compare
Updated to take advantage of |
if (_.isArray(obj)) { | ||
accumulator = []; | ||
} else if (_.isObject(obj)) { | ||
var Ctor = obj.constructor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this conditional path really necessary? If the dev wants the return to be an instance of obj
's class, why not leave it to them to pass in the proper accumulator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it adds support for other native types as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meaning the typed arrays? If so, we might be able to do a good enough job by reordering the obj
and accumulator
conditionals:
if (obj == null) return accumulator != null ? accumulator : {};
if (accumulator == null) accumulator = obj.length === +obj.length ? [] : {};
It would generalize array-likes to an array, leaving it up the dev to be more specific if they want it.
I'm not particularly against this, I just think grabbing .constructor.prototype
is ugly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypedArrays, Map, Set, etc., including anything created in user-land as well. Inferring the type is one of the things that makes transform
so nice.
_.transform(new Backbone.Model, function() { /* ... */ }) instanceof Backbone.Model // >> true
I agree that accessing .constructor.prototype
isn't very nice, but only if the user is doing something gross with it in the first place, in which case you can still specify the accumulator. Lodash's implementation works this way so @jdalton can speak to any problems that have come up because of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only issue I can see with that method is constructor overrides, but thats cool by me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdalton: Your use cases for it would be valuable.
Underscore can't iterate a Map or a Set, so it still seems like the only gain over my above comment is instanceof
? I'm 50-50 for supporting it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The create
use of _.transform
came before the actual _.create
method for me. It's a nice to have. The rationale for having it is you're transforming a value so the result should be of the same constructor. I've taken this approach in our clone method too preferring clones to be of the realm their original values. For me the biggest win is the ability to exit early and not providing a default accumulator so the create
can be seen as a non-critical requirement that could be added at a later time.
6aaa958
to
ce5ff48
Compare
ce5ff48
to
0bfc402
Compare
Looks good to go and I'm excited for this addition, ping @jashkenas |
I'm merging this if noone objects |
Excited to see this land. |
_.transform
is a useful alternative to_.reduce
that calls the iterator withaccumulator
object each time and then returns it.This is useful for creating new objects applying transformations of another object.
There was a previous issue on a similar function with the same name, however I think lodash has fully fleshed it out and is much more useful in this form.
I've taken the lodash implementation and tried to make it consistent with some of underscore's behavior.
Ref: @megawac marionettejs/backbone.marionette#2009 (comment)