Underscore.js: how to chain custom functions

10,856

Solution 1

Not finding a tap that returns the value returns by the function is runs, I define one which I can take and add to _:

_.mixin({take: function(obj, interceptor) {
    return interceptor(obj);
}});

Then assuming I have:

function double(value) { return value * 2; };

I can write:

_([42, 43]).chain()
    .first()             // 42
    .take(double)        // Applies double to 42
    .value()             // 84

You can look at take as map on objects, instead of lists. Want to experiment with this? See this example on jsFiddle.

Solution 2

So you have a custom function:

function double(value) { return value * 2; }

You can use mixin to extend Underscore with it:

_.mixin({ double:double });

Now you can call your function from the Underscore object _:

_.double(42); // 84

and from the wrapped object returned from chain:

_([42, 43]).chain()
  .first()
  .double() // double made it onto the wrapped object too
  .value(); // 84

Solution 3

Alright, I'm fresh off of reading the underscore annotated source code for the first time. But I think you can do something like this:

function double(value) { return value * 2; };

var obj = _([42, 43]).addToWrapper({double:double});

obj.chain()
  .first()
  .double()
  .value();

The syntax/details might not be right, but the core point is this: when you call _([42,43]), you're calling underscore as a function. When you do so, it instantiates a new object and then mixes into that object most of the underscore functions. Then, it returns that object to you. You can then add your own functions to that object, and none of this pollutes the "_" namespace itself.

That's what the underscore.js code looked like to me. If I'm wrong, I'd like to find out and hopefully someone will explain why.

EDIT: I've actually been using underscore.js heavily for about a month now, and I have gotten pretty familiar with it. I now know it behaves like I said here. When you call _ as a Constructor function, you get back your own "namespace" (just an object), and you can add things to it with addToWrapper() that show up in your namespace but not in the "global" "_" namespace. So the feature the OP wanted is already built in. (And I have been really impressed with underscore, btw, it is very very nicely done).

Share:
10,856

Related videos on Youtube

avernet
Author by

avernet

Co-founder of Orbeon, developing Orbeon Forms: web forms, open source, for the enterprise. Passionate about technology, and how it improves the world.

Updated on March 16, 2020

Comments

  • avernet
    avernet about 4 years

    Using Underscore.js, I can write the following which returns 42:

    _([42, 43]).chain()
        .first()
        .value()
    

    I have custom function, not part of Underscore.js called double():

    function double(value) { return value * 2; };
    

    I would like to be able to call this function in an Underscore chain, as if it was part of Underscore. I would like to write the following, which I'd like to return 84:

    _([42, 43]).chain()
        .first()
        .double()
        .value()
    

    This can't work since Underscore doesn't define double(). I could use tap() as in:

    _([42, 43]).chain()
        .first()
        .tap(double)
        .value()
    

    This is valid, but tap applies the function to its argument and returns the argument, not the result of the function. So it looks to me like I would need a sort of tap that returns the result of the function applied to its argument. Is there anything like this in Underscore.js? Am I missing something terribly obvious?

    • Christian C. Salvadó
      Christian C. Salvadó over 13 years
      Be aware that double is a future reserved word and implementations may throw a SyntaxError if it's used as an Identifier.
    • Charlie Flowers
      Charlie Flowers over 13 years
      Alessandro, have you figured out yet that I gave you the only correct answer to this question? What I laid out does not pollute the underscore namespace at all. The underscore.js library anticipated what you asked for and built in a feature for exactly that.
    • CMircea
      CMircea over 9 years
      Underscore now has both tap (runs a function to modify each item) and map (runs a function that returns a new item).
    • avernet
      avernet over 9 years
      @CMircea As of 2014-08-25, I don't see any indication that tap() can be used to modify the wrapped object. Am I missing something? underscorejs.org/#tap
  • avernet
    avernet over 13 years
    yes, that's one way of doing it. Something I don't like about this is that it pollutes _. I.e. this isn't workable if you use chaining all over the place in you code, and hence have a lot of those functions.
  • avernet
    avernet over 13 years
    I suggested an alternative that requires me to add just one function to _ - see the answer to my own question. I will experiment with this and see how well this works in practice.
  • Ibrahim Quraish
    Ibrahim Quraish over 13 years
    apply might be a better name
  • avernet
    avernet over 13 years
    right, map doesn't cut it as it only works on list. Here I need a sort-of map that works on single "items".
  • avernet
    avernet over 13 years
    @Gabe, I thought of calling it apply, but the name is already defined on functions, and it has a different semantic: f.apply(obj, argArray) instead of obj.take(f, argArray). You don't think this could be confusion? (Honestly asking as I am not convinced myself either way.)
  • Sasha Chedygov
    Sasha Chedygov over 13 years
    I agree that this is way more ugly than the other solution you posted.
  • avernet
    avernet over 13 years
    Charlie, yes, you could do this. I should have mentioned this in my question: I don't want to do it as it would pollute the _ namespace. (Imagine if you have a large codebase using Underscore, and each time you want to do this you need to add the function to Underscore.)
  • Charlie Flowers
    Charlie Flowers over 13 years
    @Alessandro Vernet - no it will not pollute the _ namespace. That is the beauty of it. When you call _([42,43]), it returns a brand new namespace just for you. When you then add "double" to it, your own namespace will have double, but _ itself will not.
  • Charlie Flowers
    Charlie Flowers over 12 years
    @Cesar Canassa - fair enough. That's why I said you can do something "like" this. I wasn't in front of my development machine with all the tools installed when I typed this. The point is that calling _ as a function returns a new object and won't pollute the "usual" _ namespace. Turns out, addToWrapper does not return this new object, so you have to add another line, as in: var obj = _([42, 43]); obj.addToWrapper(whatever); obj.chain().first().double().second(). And even that I can't certify as exactly right. The concepts are right, but I don't have a convenient place to check exact syntax.
  • Ishmael Smyrnow
    Ishmael Smyrnow almost 11 years
    For those looking, tap is now part of underscore core.
  • arcseldon
    arcseldon over 9 years
    @avernet you could simply prefix and postfix map with first.
  • saiyancoder
    saiyancoder about 3 years
    A little late to the party, but how about naming it tapAndReturn?