underscore icon indicating copy to clipboard operation
underscore copied to clipboard

Shorthand for piping a value through a bunch of functions

Open tfga opened this issue 6 years ago • 8 comments
trafficstars

Let's say you want to pipe some value a through 3 functions f1, f2, f3, in that order. How would you do it?

Well, you can do it the "normal" way (pure JS):

f3(f2(f1(a)))

Or you can use _.compose():

_.compose( f3
         , f2
         , f1
         )(a)

But what I'd really like to write is:

_.pipe( a
      , f1
      , f2
      , f3
      )

Both JS's function composition syntax and _.compose() make me write the function list in the reverse order. Clojure has threading macros; in Elm, you could write this using the reverse apply operator:

a
|> f1
|> f2
|> f3

This _.pipe() function I'm suggesting would be a way of doing something similar in JS.

tfga avatar May 30 '19 21:05 tfga

Somebody implemented this as a babel macro:

https://github.com/Andarist/pipeline.macro

tfga avatar Jul 16 '19 09:07 tfga

@tfga You can easily implement this yourself and then add it to Underscore if you want:

var pipe = _.restArguments(function(input, functions) {
    return _.compose.apply(null, functions.reverse())(input);
});

_.mixin({pipe: pipe});

_.pipe(a, f1, f2, f3);

Or with ES6 syntax:

function pipe(input, ...functions) {
    functions.reverse();
    return _.compose(...functions)(input);
}

_.mixin({pipe});

_.pipe(a, f1, f2, f3);

jgonggrijp avatar Apr 07 '20 19:04 jgonggrijp

@jgonggrijp But then I'd have to duplicate this code in every project. To prevent that, isn't it the point of libraries like underscore?

tfga avatar Apr 08 '20 02:04 tfga

No, you don't have to copy it in every project. You can make your own NPM package that imports Underscore, adds your desired functions to it with _.mixin, and then exports the result as a "super Underscore". If you use that package everywhere instead of Underscore itself, no code gets duplicated and you get all original functions of Underscore unmodified as well as your own additions. Win-win!

jgonggrijp avatar Apr 08 '20 08:04 jgonggrijp

@jgonggrijp Ok. Thanks for the replies.

tfga avatar Apr 09 '20 13:04 tfga

@tfga I guess I should have started by mentioning why I think this doesn't belong in Underscore. Sorry for not doing that.

I see the added value of _.pipe. It also combines well with chaining:

_.chain(a).pipe(f1, f2, f3).pipe(f4, f5).value();

But the problem is, there is an infinite space of functions that are not in Underscore but that would have an added value. We can't add all of them. In fact, we have to be conservative because Underscore is trying not to grow fatter (see #2060).

I'm not ruling out that new functions would be added (in fact that's not up to me to decide), but given that pipe is just reverse + compose, to me it doesn't seem like a game-changing addition.

Another thing I failed to mention is that there is a very similar function available from Underscore-Contrib: pipeline.

jgonggrijp avatar Apr 09 '20 17:04 jgonggrijp

@jgonggrijp No worries. Thank you for taking the time to explain this. I appreciate it.

tfga avatar Apr 10 '20 09:04 tfga

Reopening this, because I'm seriously considering to replace chain by pipe in future Underscore 2.0. Motivation:

  • chain only works with functions that have been added through mixin, while pipe can work with any function if implemented well.
  • chain prevents treeshaking (because of mixin), pipe doesn't.

Ditching chain might also imply ditching the OOP notation and fully committing to functional notation instead. Discussion welcome.

FYI @tfga.

jgonggrijp avatar Sep 07 '22 08:09 jgonggrijp