Piping is composing functions together, feeding the return of one function into the parameters of the next:

let output = doThis(doThat(doTheOtherThing(input)))

Isn't that clear? There are real benefits to modeling as much of your logic as possible on data flow pipes like this. Like, refactoring become a snap:

let output = oneMoreChange(doThis(doThat(doTheOtherThing(input))))

Do all these functions have to take one parameter? Yes. There is no free lunch. You only get the benefits of pipes if you bend your logic into pipe-like shapes.

Piperoni helps you do that:

Fashion pipes with familiar, flexible chaining: myPipe.doThis().doThat.doTheOtherThing()

Mix sync and async functions indiscriminately: myPipe.doAsyncThing().doSyncThing().doAnotherAsyncThing()

Use utilities from Ramda, which gives every pipe the power of lodash or underscore: myPipe.zip().map().pluck().etc()

Group, then share, related functions: Creating methods from functions: myPipe.methize(doThis, doThat, doTheOtherThing) Turning pipes into methods of other pipes: myPipe.methize(myOtherPipe.funcize()) Cloning to support variations: let myPipe = myOtherPipe.clone()

Handle logic that doesn't fit neatly into the data-flow model, i.e, parallelism, conditionals, and no-ops, with tag methods: myPipe . doThis ( ) . startParallel ( ) . doAsync ( 100 ) . doAsync ( 1000 ) . doAsync ( 10000 ) . stopParallel ( ) . doTheOtherThing ( ) startParallel and stopParallel are "tag" functions because, like markup tags, they apply special logic to their inner contents (here, methods calls).

Simple Composition

"c" is for "compose"

const makePipe = require ( " piperoni " ) . makePipe const assert = require ( " assert " ) function addOne ( x ) { return x + 1 } function addTwo ( x ) { return x + 2 } const pipe = makePipe ( ) pipe . in ( 0 ) . c ( addOne ) . c ( addTwo ) . out ( ( err , num ) => { assert ( num === 3 ) } )

If you've piped before, you can guess the next optimization. The spice of Javascript is...curry:

function add ( y ) { return x => x + y } pipe . in ( 0 ) . c ( add ( 1 ) ) . c ( add ( 2 ) ) . out ( ( err , num ) => { assert ( num === 3 ) } )

Currying is huge for piping. With currying, you can:

Ratchet a function down to one parameter.

Vary a function to produce other functions quickly.

Concentrate and express state in one place orthogonal to the data flow.

Creating Methods

If we could compose functions by calling chainable methods, wouldn't that look cleaner? Now you can:

pipe.methize(add) pipe.in(0).add(1).add(2).out((err, num) => assert(num === 3))

Remember that the method is a higher-order function. add is not being composed, the function it produces is the one actually getting piped:

HIGHER-ORDER or Currying Function

function add ( y ) { return x => x + y }

COMPOSED or Curried Function

x => x + y

Mixins

Piperoni allows you to group functions together as methods of a pipe. Once they populate pipes, they can also be convenientially shared between pipes.

Suppose you've created this pipe to manipulate files...

let filePipe = makePipe().methize(stats, readFiles, copyFiles, moveFiles)

..and this pipe to connect to a Restful API...

let restPipe = makePipe().methize(create, read, update, destroy)

...and you need functionality from both pipes:

const myPipe = makePipe ( ) . mixin ( filePipe , restPipe ) myPipe . in ( " /myDirName " ) . stats ( ) . filter ( weedOutByExtension ) . c ( oneOffMunging ) . readFiles ( ) . create ( " SavedFilesResource " ) . out ( ( err , message ) => { assert ( message . status === " ok " ) } )

Note: find a complete list of Ramda utilities at Ramda Documentation. All functions are exposed as pipe methods, but only the higher-order ones will work as methods. The other ones can be accessed at:

let Piperoni = require ( " piperoni " ) console . log ( Piperoni . r . someNonCurryingFunction )

Reusing, Composing and Cloning Pipes

A pipe has two aspects: its methods, and the composition built up by the methods. Mixins are how you repeat the methods. Now let's repeat the composition.

Every time a pipe is "outed," a new composition is executed. So the same pipe can be outed repeatedly:

pipe . add ( 1 ) . add ( 2 ) pipe . in ( 10 ) . out ( ( err , sum ) => assert ( sum === 13 ) ) pipe . in ( 100 ) . out ( ( err , sum ) => assert ( sum === 103 ) )

This pipe can also be used as a one-off function in another pipe...

let addThree = pipe . exec otherPipe . in ( 0 ) . c ( addThree ) . out ( ( err , num ) => assert ( num === 3 ) )

...or transformed into a higher-order function and added as a method to another pipe:

otherPipe . methize ( pipe . funcize ( " addThree " ) ) otherPipe . in ( 0 ) . addThree ( ) . out ( ( err , num ) => assert ( num === 3 ) )

Lastly, we can duplicate the pipe so the duplicate can diverge from the original composition:

let oldPipe = makePipe . methize ( add ) . add ( 1 ) . add ( 2 ) let newPipe = oldPipe . clone ( ) oldPipe . add ( 10 ) newPipe . add ( 100 ) oldPipe . in ( 0 ) . out ( ( err , sum ) => assert ( sum === 13 ) ) newPipe . in ( 0 ) . out ( ( err , sum ) => assert ( sum === 103 ) )

To clear the composition from a pipe, clear it.

let newPipe = oldPipe.clone().clear()

Async

Everything above and below applies to async as well as sync methods and functions. However, Piperoni does need to differentiate between async and sync functions to compose them. Consequently, just as all sync functions should be curried to take one parameter, all async functions should take two.

function add ( increment ) { return num => num + increment } function waitAndAdd ( increment , waitTime ) { return ( num , callback ) = { setTimeout ( ( ) = > { callback ( null , num + increment ) } ) , waitTime) } }

Piperoni will use the callback internally to execute a composition.

Tag Methods For Non-Linear Logic

Here is a tag method at work:

myPipe . doThis ( ) . startParallel ( ) . doAsync ( 100 ) . doAsync ( 1000 ) . doAsync ( 10000 ) . stopParallel ( ) . map ( processThreeResults )

Tag methods step outside the linear flow of data from composed function to composed function. In the code above, the three functions created by doThat are invoked in parallel and the results forwarded as an array to map.

Another out-of-the-box tag method is which, which chooses "which" of the composed functions should be invoked.

myPipe . doThis ( ) . startWhich ( chooseFuncIndex ) . doAsync ( 100 ) . doAsync ( 1000 ) . doAsync ( 10000 ) . stopWhich ( ) . map ( processThreeResults )

No-ops/pass-throughs are also very possible:

myPipe . doThis ( ) . startMaybe ( ifNecessary ) . startWhich ( chooseFunc ) . doAsync ( 100 ) . doAsync ( 1000 ) . doAsync ( 10000 ) . stopWhich ( ) . map ( processThreeResults ) . stopMaybe ( )

Moving conditional and non-linear logic logic out of composable functions and into Piperoni keeps the functions simpler and more reusable.

Adding your own tag methods to a pipe is trivial. Just come up with a name and an associated function that transforms the array of inner functions (the three functions produced by doAsync) into a new array of functions.

Let's say we want a tag method that repeats a function in a composition. It would work like this:

myPipe . startTimes ( 5 ) . add ( 1 ) . add ( 1 ) . stopTimes ( ) . in ( 0 ) . out ( ( err , num ) => assert ( num === 10 ) )

Here's how we could make the tag method:

function times ( funcs , params ) { let newFuncs = [ ] ; const num = params [ 0 ] for ( var i = 0 ; i < num ; i ++ ) { newFuncs = newFuncs . concat ( funcs ) ; } return newFuncs ; } myPipe . tagize ( { times : times } )

Tag logic often has to treat sync and async functions separately. Consult Piperoni's parallel and which implementations for guidance.