6

I'm learning functional programming and I wonder if there is a way to "combine" functions like this:

function triple(x) {
    return x * 3;
}
function plusOne(x) {
    return x + 1;
}
function isZero(x) {
    return x === 0;
}
combine(1); //1
combine(triple)(triple)(plusOne)(1); // 10
combine(plusOne)(triple)(isZero)(-1); // true

If the para is a function, it "combines" the function into itself, and if not it will return the final result. Thanks!

3
  • @u_mulder parameter i guess Commented May 12, 2015 at 17:55
  • the question is not clear at the point of "combines" the function Commented May 12, 2015 at 18:00
  • the following code works since it returns a function ... x = function(){return function(b){return b*2}}; x()(2) == 4 ... not what you want though Commented May 12, 2015 at 18:02

5 Answers 5

42

heritage

This is a concept from maths called function composition.

       f(x) = y
       g(y) = z

    g(f(x)) = z

   (g•f)(x) = z

That last line is read "g of f of x equals z". What's great about composed functions is the elimination of points. Notice in g(f(x)) = z we take an x input and get a z output. This skips the intermediate point, y.

Composition is a great way to create higher-order functions and keep your code sparkly clean. It's plain to see why we'd want this in our Javascript programs.


comp

JavaScript is a multi-paradigm language with rich support for functions. We can create a simple comp function, which combines two input functions, g and f, and results in a new function -

function triple(x) {
  return x * 3
}

function plusOne(x) {
  return x + 1
}

function comp(g, f) {
  return function(x) {
    return g(f(x))        // "g of f of x"
  }
}

const myfunc =
  comp(triple, plusOne)

console.log(myfunc(1))

Evaluation

triple(plusOne(1))
triple(2)
6

compose

Just as the question suggests, it's likely we will want to combine more than two functions. Below we write compose which takes all of the input functions and reduces them using our simple comp from above. If no functions are given, we return the empty function, identity -

const triple = (x) =>
  x * 3

const plusOne = (x) =>
  x + 1

const comp = (g, f) =>
  x => g(f(x))                     // "g of f of x"

const identity = (x) =>
  x

const compose = (...all) =>
  all.reduce(comp, identity)

const myfunc =
  compose(triple, triple, plusOne) // any amount of funcs

console.log(myfunc(1))

Evaluation

triple(triple(plusOne(1)))
triple(triple(2))
triple(6)
18

pipe

You can be as creative as you like. Below, we write pipe which allows our programs to read in a comfortable left-to-right direction -

const triple = (x) =>
  x * 3

const plusOne = (x) =>
  x + 1

const pipe = x =>
  f => pipe(f(x))

pipe(1)(plusOne)(triple)(triple)(console.log)           // 18
pipe(3)(triple)(plusOne)(triple)(plusOne)(console.log)  // 31

Evaluation of expression one -

f => pipe(f(1))
pipe(plusOne(1))
f => pipe(f(2))
pipe(triple(2))
f => pipe(f(6))
pipe(triple(6))
f => pipe(f(18))
pipe(console.log(18))
18

and expression two -

f => pipe(f(3))
pipe(triple(3))
f => pipe(f(9))
pipe(plusOne(9))
f => pipe(f(10))
pipe(triple(10))
f => pipe(f(30))
pipe(plusOne(31))
f => pipe(f(31))
pipe(console.log(31))
31

related techniques

Curried functions and partial application are concepts that gel with function composition. pipe above is introduced in another Q&A as $ and demonstrated again here -

const $ = x =>           // "pipe", or whatever name you pick
  k => $ (k (x))
  
const add = x => y =>    // curried add
  x + y

const mult = x => y =>   // curried mult
  x * y
  
$ (1)                    // 1
  (add (2))              // + 2 = 3
  (mult (6))             // * 6 = 18
  (console.log)          // 18
  
$ (7)                    // 7
  (add (1))              // + 1 = 8
  (mult (8))             // * 8 = 64
  (mult (2))             // * 2 = 128
  (mult (2))             // * 2 = 256
  (console.log)          // 256

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

7 Comments

Excellent! I spent a long time reading it and it helps me a lot. Thanks for your great work!
@user3016883 I found a couple typos and fixed them up. If you ran into trouble, I hope the edits help ^.^
great answer, but it's quite missing the fact that as long as the first param is a function, then it should curry indefinitely: stackoverflow.com/a/65537611/4099454. whereas your version does not seem to curry at all
Hitmands, I've been meaning to rework this answer for some time. Thanks to your respected review I finally got around to it :D
|
1
function triple(x) {
    return x * 3;
}
function plusOne(x) {
    return x + 1;
}
function isZero(x) {
    return x === 0;
}

var combine = function (v) {
    var fn = [];
    function _f(v) {
        if (typeof v === 'function') {
            fn.push(v);
            return _f;
        } else {
            return fn.reduce(function (x, f) { return f(x); }, v);
        }
    }
    return _f(v);
};

var a, b;
console.log(combine(1)); //1
console.log(combine(triple)(triple)(plusOne)(1)); // 10
console.log(combine(plusOne)(triple)(isZero)(-1)); // true
console.log(a = combine(plusOne)); // function ...
console.log(b = a(triple)); // function ...
console.log(b(5)); // 18
console.log(combine(triple)(plusOne)(triple)(plusOne)(triple)(plusOne)(1)); // 40
// @naomik's examples
var f = combine(triple); 
var g = combine(triple)(triple); 
console.log(f(1)); // 3
console.log(g(1)); // 9 (not 6 as you stated)

7 Comments

Make two functions: var f = combine(triple); var g = combine(triple)(triple); Call f(1); // 27, then call g(1) // 1. Obviously f(1) // should be 3 and g(1) // should be 6. This fails because you're depending on the user to finish a chain before creating a new one. The problem is combine.fn is stateful and subsequent calls to combine will result in the fn state of other functions being mutated.
i know this problem, but what result do you expect with combine(triple)(triple);? the call ends alway with a value, so the rule is function* value (bfn).
i expect combine(triple)(triple) to create a new function for me that i can assign to variable, call at a later time, or pass as a value to another function. Just as I demonstrated with f and g above.
I've removed my down-vote as you've provided an answer that actually works. If this type of interface is insisted upon, please see this gist for a more functional implementation. I've attached some other notes there. ^.^
i do not insit of the interface, hence i changed it to just one inital function and the return function.
|
1

Function composition has already been detailed in other answers, mostly https://stackoverflow.com/a/30198265/4099454, so my 2 cents are straight onto answering your latest question:

If the para is a function, it "combines" the function into itself, and if not it will return the final result. Thanks!

const chain = (g, f = x => x) => 
  typeof g === 'function'
  ? (y) => chain(y, (x) => g(f(x)))
  : f(g);

// ====

const triple = x => x * 3;
const inc = x => x + 1;
const isZero = x => x === 0;

console.log(
  chain(inc)(triple)(isZero)(-1),
);

1 Comment

Nice work matching the OP's exact proposal. Beware type-checking inputs and giving special behaviour to functions means chain can no longer accept functionals as arguments. For example, often times we need to compute a function, such as a thunk or a continuation, and pass it as an argument to another function. In this setup, chain would hazardously compose the functional instead of passing it as an argument to the composition.
0

It is also possible to build a complex Functionality by Composing Simple Functions in JavaScript.In a sense, the composition is the nesting of functions, passing the result of one in as the input into the next. But rather than creating an indecipherable amount of nesting, we'll create a higher-order function, compose(), that takes all of the functions we want to combine, and returns us a new function to use in our app.

function triple(x) {
  return x * 3;
}
function plusOne(x) {
  return x + 1;
}
function isZero(x) {
  return x === 0;
}

const compose = (...fns) => x =>
  fns.reduce((acc, cur) => {
    return cur(acc);
  }, x);

const withCompose = compose(triple, triple, isZero);
console.log(withCompose(1));

1 Comment

compose should be right to left, you wrote a pipe instead
-1

You can simply call the function on the return values themselves, for example:

plusOne(triple(triple(1))) // 10
isZero(triple(plusOne(-1))) // true

4 Comments

Could you please explain why my answer is so poor down-voter?
I didn't down-vote your post, but it doesn't really answer the question. The OP wants to programmatically build a "chain" or "pipeline" of functions. It is called function composition and javascript doesn't have a built-in function for it. Your answer is likely telling the OP something he/she already knows how to do.
@naomik I would say it depends on what the OP means as "combines", and if the OP already knew that solution I would imagine they would comment that. I'm simply stating you can get the correct results by performing f(g(x)) other than making a functions that is F = f(g(x)), assuming that the OP may be a beginner and perhaps might not know that. It does (in a way) combine the functions and does indeed produces the correct results the OP wanted, so I don't see how it's not an answer.
@naomik Although I could of just misunderstood the question, seems clear your answer is what the OP was looking for.

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.