1

How can I have a JavaScript function let's say piper() which takes several functions as its arguments and it returns a new function that will pass its argument to the first function, then pass the result to the second, then pass the result of the second to the third, and so on, finally returning the output of the last function.

Something like piper(foo, fee, faa)(10, 20, 30) would be equivalent to calling faa(fee(foo(10,20,30))).

ps: It was a part of an interview, that I did few days ago.

13
  • 1
    Have you tried anything yourself? Commented Oct 12, 2016 at 20:24
  • ramdajs.com/docs/#pipe Commented Oct 12, 2016 at 20:25
  • @MikeC I am used to see functions in js like this function ali( saberi,i) {saberi(i)}, not in this way.with 2 paranthesis Commented Oct 12, 2016 at 20:27
  • 1
    @AliSaberi Remember: you can return functions from a function. See this question. Commented Oct 12, 2016 at 20:31
  • 1
    Like a lot of interview questions, if you ever code like this you deserve to fail a code review. This is absolutely nuts. f(...)(...)(...) is not readable at all. As an academic exercise the hints are in your question: it "returns a new function". Commented Oct 12, 2016 at 20:33

7 Answers 7

10

For an arbritrary number of functions you could use this ES6 function:

function piper(...fs) {
    return (...args) => fs.reduce((args,f) => [f.apply(this,args)],args)[0];
}
// Example call:
var result = piper(Math.min, Math.abs, Math.sqrt)(16, -9, 0)
// Output result:
console.log(result);

The same in ES5 syntax:

function piper(/* functions */) {
    var fs = [].slice.apply(arguments);
    return function (/* arguments */) { 
        return fs.reduce(function (args,f) {
            return [f.apply(this,args)];
        }.bind(this), [].slice.apply(arguments))[0];
    }.bind(this);
}
// Example call:
var result = piper(Math.min, Math.abs, Math.sqrt)(16, -9, 0)
// Output result:
console.log(result);

Sign up to request clarification or add additional context in comments.

17 Comments

f(...args) would be shorter, but I like that you made it preserve context.
So basically, It's ES6 problem and solution, right?
@Vic is right. This is not an ES6 problem (is there such a thing?), but a solution represented in ES6 syntax.
Added ES5 equivalent to my answer.
ES6 solution pins this to the global scope because of the arrow functions. Which is not good. ES5 example leaks arguments, which deoptimize the function. Otherwise, it's fine.
|
2

Enjoy. Pure ES5 solution. Preserves this.

function piper(){
    var i = arguments.length,
        piped = arguments[ --i ];

    while( --i >= 0 ){
        piped = pipeTwo( arguments[ i ], piped );
    }

    return piped;
}

function pipeTwo( a, b ){
    return function(){
        return a.call( this, b.apply( this, arguments ) );
    }
}

Or, if you want the fancy solution.

function piperES6( ...args ){
    return args.reverse().reduce( pipeTwo );
}

Loops can be reversed depending on the desired direction.

Comments

1

Very similar to @trincot's answer (preserves context), but composes in the correct order and is marginally faster since it does not create intermediary arrays:

const piper = (...steps) => function(...arguments) {
  let value = steps[0].apply(this, arguments);
  for (let i = 1; i < steps.length; ++i) {
    value = steps[i].call(this, value);
  }
  return value;
};



// Usage:

let p = piper(
  x => x + 1,
  x => x * 2,
  x => x - 1
);

console.log(p(2)); // 5

Comments

1

Here is an alternative answer involving method chaining. I shall use ES6, though of course this can be transpiled to ES5. On benefit of this solution is that is has a very succinct TypeScript counterpart with perfect typeability.

class Pipe {
    constructor(value) {
        this.value = value;
    }
    then(f) {
        return new Pipe(f(this.value));
    }
}
    
const pipe = value => new Pipe(value);
    
// Example

const double = x => 2 * x;
    
pipe(42).then(double).then(console.log); // 84

const result = pipe(42).then(double).then(double).value;
console.log(result); // 168

Comments

0

A simple solution based on JS higher-order functions usage:

function pipe(...rest) {
  return x => rest.reduce((y, f) => f(y), x);
}

Usage:

pipe((a) => a + 1, (a) => a * 2)(3) // 8
pipe((a) => a + 1, (a) => a * 2)(2) // 2

Comments

-3
function f(f1, f2, f3){

    return (args => f3(f2(f1(args))));

}

2 Comments

Doesn't work for an arbitrary number of parameters. Throws exception when used with two of them. Interview failed ;)
i gave an example with 3 functions, but we can pass an array of functions.
-3

I think what you are trying to do is chaining.

var funct={
    total:0,
    add:function(a) {
        console.log(funct.total,funct.total+a);
        funct.total+=a;

        return funct;
    }
};


funct.add(5).add(6).add(9);

1 Comment

the method must be called like this: piper(foo, fee, faa)(10, 20, 30)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.