Skip to content

Instantly share code, notes, and snippets.

@ratbeard
Created February 24, 2010 05:49
Show Gist options
  • Save ratbeard/313142 to your computer and use it in GitHub Desktop.
Save ratbeard/313142 to your computer and use it in GitHub Desktop.
I've got some experimental stuff in unpublished branches / projects I use underscore in. Let me know if
you like any of these:
1.) Too many simple things take two lines to do in javascript, like:
_.reduce(list, {}, function (memo, value) {
memo[value] = true;
return memo;
});
_.reduce(list, [], function (memo, value) {
memo.push(value);
return memo;
});
function simpleApiWrapper(key, value) {
var o = {};
o[key] = val;
apiCallThatTakesAConfigObject(o);
}
These guys can solve them, and are jqueryish:
_.prop = function (obj, key, value) {
if (value !== undefined) {
obj[key] = value;
return obj;
}
return obj[key];
};
_.push = function (array, value) {
array.push(value);
return array;
}
Now:
_.reduce(list, {}, function (memo, value) {
return _.prop(memo, value, true);
});
_.reduce(list, [], function (memo, value) {
return _.push(memo, value);
});
function simpleApiWrapper(key, value) {
apiCallThatTakesAConfigObject(_.prop({}, key, value));
}
Also, a version of reduce that doesn't require you to return the memo is nice. You lose some functional purity, but its how reduce is used practically all the time. Thats how its implemented in jspec. Now:
_.managedReduce(list, {}, function (memo, value) {
memo[value] = true;
});
Yes, that name sucks.
2.) _.extend() should accept a variable number of source objects. Like jquery, closure, and ext versions.
3.) let filter() and friends take flexible options:
_.filter(array, function (el) { return el.prop == 'hi' });
_.filter(array, {prop: 'hi'});
_.filter(array, 'prop', 'hi');
Apply to some, every, detect, reject, etc., and map (replacing pluck).
I just prototyped this in a branch: http://github.com/ratbeard/underscore/tree/friendly_args
Needs to be implemented as some function builders, which would be useful to have on underscore as a public function (similar to _.identity).
4.) I've needed an _.isObject()
5.) I've needed a groupBy() method like: http://api.rubyonrails.org/classes/Enumerable.html#M002570
6.) I've needed sum()
_.sum = function (obj) {
return _.reduce(obj, 0, function (acc, cur) { return acc + cur; });
};
7.) Change API of _.each to not return obj. Returning obj now for chaining seems pointless to me, unless wrapper requires it? This leaves the door open for making a fun delayed query interface, ala LINQ or datamapper.
_.each(students).filter('age').lt(7).all();
var fn = _.each(students).filter('age').lt(7).map('name'); // doesn't call yet
fn.first(); // call now!
==========================================================================
I've been working more lately on more project infrastructure stuff, like:
- being able to turn off native versions of fn's to test our implementations, since I had a bug in one of
my implementations a while back that I never realized as I was just using the native version.
- inline documentation. I tried both pdoc and jsdoc, and both were painful. jsdoc was more mature, but:
- having a separate compile phase sucks
- its java-oriented classes, inheritence, etc. is way overkill for underscore
- Templates were complex
- making a custom tag `@native 1.6` isn't simple, and the data isn't extractable for testing purposes
I started to think that the best way would just be to have inline documentation as JSON objects, and
generate the docs page on the fly with the templating function. e.g:
documentation.some = {
desc: 'Determine if at least one element in the object matches a truth test.',
nativeIn: '1.6',
alias: 'any'
};
_.some = function(obj, iterator, context) {
...
};
Of course we would strip this out of the min versions, but this way we can use this data for
documentation AND testing all the permutations (native versions turned off, test all aliases for free)
This led me down the path of experimenting with the closure compiler, which I'd never used. I started
to play around with advanced optimizations when compiling with user code, to remove dead functions that
aren't called, which is probably the majority of them for most users. There are a few gotchas in the
compiler at this point
(http://groups.google.com/group/closure-compiler-discuss/browse_thread/thread/b59b54c1a0073aa5?pli=1),
but I got it working tonight where it crunches down to ~100 bytes if none of the functions are called.
If a user calls _.map() in their code, then that and all its dependencies are pulled in, which is
pretty sweet. This way we could keep _.buildLookup(), since its pretty useful but will disappear for
users that don't call it :) alias was stupid, I reverted that in my closure branch.
One change I had to make to crunch down a lot of potentially dead code is to make the user explicitly say
they want the OO wrapper stuff by calling `_.initWrapper()`. Otherwise, there was no other way to remove
that code as its referenced in the underscore constructor, and it was pulling in ~1700 bytes if I recall.
I make a call to `_.initWrapper` at the bottom of the file to maintain backwards compatability, which I
just remove in my :build_advanced rake task, so as long as a user compiles with my rake task they won't
get the wrapper code unless they ask for it. Or if the user comments that line out. Before 1.0, maybe you
want to break backwards compatibility to make advanced optimizations easier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment