Closed davidchambers closed 10 years ago
:+1: To this idea. I'd also like to see unary versions of these functions.
I've tackled this by using _.partial
with placeholders. See demo.
_.map([1,2,3], _.partial(_.range, _, null, 1)));
// => [[0],[0,1],[0,1,2]]
I've also specialized some methods (clone, extend, defaults, flatten, uniq, max, min, merge, sample) to work out of the box with smth like this. Underscore does this with _.first
too.
With placeholders and _.partial
there's no need for dup-ish method implementations.
Here's a concrete example:
_.chain($('a'))
.map(cheerio)
.invoke('attr', 'href')
.filter(Boolean)
.reject(_.compose(
_.partial(nucleotides.string.indexOf, _, 'quux.do?'),
_.identity))
.sort()
.uniq(true)
.map(_.compose(
_.partial(nucleotides.string.concat, origin, '/foo/bar/'),
_.identity))
.value()
What I'd like to write:
_.chain($('a'))
.map(cheerio)
.invoke('attr', 'href')
.filter(Boolean)
.reject(_.partial(nucleotides.string.indexOf, _, 'quux.do?'))
.sort()
.uniq(true)
.map(_.partial(nucleotides.string.concat, origin, '/foo/bar/'))
.value()
Having "dupish" implementations of these functions (unaryMap, etc.) does seem a bit unwieldy, but so does partially applying every argument to a function. This requires me to know exactly how many arguments each function might take and also how the number of arguments affects their behavior (the _.range function is a good example).
A good compromise might be to come up with an _.unary
function that accepts a multiary function and returns a unary version. The above example could get pretty close to what's desired:
_.chain($('a'))
.map(cheerio)
.invoke('attr', 'href')
.filter(Boolean)
.reject(_.unary(_.partial(nucleotides.string.indexOf, _, 'quux.do?')))
.sort()
.uniq(true)
.map(_.unary(_.partial(nucleotides.string.concat, origin, '/foo/bar/')))
.value()
@davidchambers smth like is doable now (pseudo code)
_.chain($('a'))
.map(cheerio)
.invoke('attr', 'href')
.compact()
.reject(_.partial(nucleotides.string.indexOf, _, 'quux.do?', 0))
.sort()
.uniq(true)
.map(_.partial(nucleotides.string.concat, origin, '/foo/bar/', ''))
.value()
@Stankalank I'm not sure the callback
arg case is complex enough to warrant a core solution. The _.partial
route seems to handle it well enough.
@jdalton, nucleotides.string.concat is variadic (like String.prototype.concat), so no amount of partial application will prevent the extra args from interfering. ;)
nucleotides.string.concat is variadic
It's fine because you just have to cover the 3 callback args and a concat of ''
will do.
That's not how _.partial works, though:
_.map(['foo', 'bar', 'baz'], _.partial(nucleotides.string.concat, 'prefix-', '', ''))
// => [
// 'prefix-foo0foo,bar,baz',
// 'prefix-bar1foo,bar,baz',
// 'prefix-baz2foo,bar,baz',
// ]
Essentially, we're doing this:
[ nucleotides.string.concat('prefix-', '', '', 'foo', 0, ['foo', 'bar', 'baz']),
nucleotides.string.concat('prefix-', '', '', 'bar', 1, ['foo', 'bar', 'baz']),
nucleotides.string.concat('prefix-', '', '', 'baz', 2, ['foo', 'bar', 'baz']) ]
Essentially, we're doing this:
Ah, you're correct. Then your _.compose
+ _.partial
alternative seems fine for variadic cases.
.map(_.compose(_.partial(nucleotides.string.concat, origin, '/foo/bar/'), _.identity))
Then your .compose + .partial alternative seems fine for variadic cases.
That, and that in many of these cases -- just go ahead and write out the function you're going to use ... it's much easier to read, ultimately:
.reject(_.compose(
_.partial(nucleotides.string.indexOf, _, 'quux.do?'),
_.identity))
vs.
.reject(function(s) {
return nucleotides.string.indexOf(s, 'quux.do?') >= 0;
})
or.
.reject (s) -> nucleotides.string.indexOf(s, 'quux.do?') >= 0
Regardless, I don't think we should have unary versions of all the functions. I could imagine an _.fixArgs
function that returns a version of a function that only calls the wrapped function with the exactly the specified number of arguments — but I think that's more Underscore-Contrib territory.
Yep. Contrib has _.fix
which can do something like that:
http://documentcloud.github.io/underscore-contrib/#fix
The additional arguments passed by .map, .filter, etc. are problematic for variadic functions and functions with optional arguments:
There's a workaround, but it's icky:
Am I the only one who often finds it necessary to
_.compose
with_.identity
in order to strip unwanted arguments?Assuming breaking changes are off the cards, perhaps
_.unaryMap
and friends could be added.