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!

user3016883
  • 164
  • 1
  • 2
  • 11

5 Answers5

40

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
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 2
    Excellent! I spent a long time reading it and it helps me a lot. Thanks for your great work! – user3016883 May 12 '15 at 19:53
  • @user3016883 I found a couple typos and fixed them up. If you ran into trouble, I hope the edits help ^.^ – Mulan May 12 '15 at 20:49
  • Related: [How to correctly curry a function in JavaScript?](http://stackoverflow.com/q/27996544/783743) – Aadit M Shah May 14 '15 at 08:57
  • great answer, but it's quite missing the fact that as long as the first param is a function, then it should curry indefinitely: https://stackoverflow.com/a/65537611/4099454. whereas your version does not seem to curry at all – Hitmands Jan 02 '21 at 12:46
  • Hitmands, I've been meaning to rework this answer for some time. Thanks to your respected review I finally got around to it :D – Mulan Jan 02 '21 at 15:54
  • I'm having a hard time seeing how to use these if you want to do something with the values besides write them to the console. `pipe(1)(plusOne)(triple)(triple)(console.log) ` correctly prints 18 to the console, but `z=pipe(1)(plusOne)(triple)(triple)` doesn't assigned the value 18 to `z`, in fact it assigns a function to `z`. How do you capture the end result in a variable to use later? – John Smith Jan 23 '22 at 08:35
  • if you want to _"do something"_, `console.log` is just an example, you can replace it with any other function that does another thing. assigning to a variable like in direct style is not the only way to write a program. if you really must get the data "out", you can modify `pipe` to check `f == null` or some other sentinel and conditionally return `x`. – Mulan Jan 24 '22 at 15:21
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)
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 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. – Mulan May 12 '15 at 20:37
  • 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). – Nina Scholz May 12 '15 at 20:46
  • 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. – Mulan May 12 '15 at 21:02
  • 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](https://gist.github.com/naomik/e222280caaf2019bde1f) for a more functional implementation. I've attached some other notes there. ^.^ – Mulan May 13 '15 at 16:29
  • i do not insit of the interface, hence i changed it to just one inital function and the return function. – Nina Scholz May 13 '15 at 17:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77752/discussion-between-rookie-and-naomik). – Nina Scholz May 13 '15 at 20:01
  • nice, you could also write the same function this way: https://stackoverflow.com/a/65537611/4099454 – Hitmands Jan 02 '21 at 12:50
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),
);
Hitmands
  • 13,491
  • 4
  • 34
  • 69
  • 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. – Mulan Jan 02 '21 at 15:52
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));
Mamunur Rashid
  • 1,095
  • 17
  • 28
-1

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

plusOne(triple(triple(1))) // 10
isZero(triple(plusOne(-1))) // true
Spencer Wieczorek
  • 21,229
  • 7
  • 44
  • 54
  • Could you please explain why my answer is so poor down-voter? – Spencer Wieczorek May 13 '15 at 01:13
  • 2
    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](http://en.wikipedia.org/wiki/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. – Mulan May 13 '15 at 04:52
  • @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. – Spencer Wieczorek May 13 '15 at 05:04
  • @naomik Although I could of just misunderstood the question, seems clear your answer is what the OP was looking for. – Spencer Wieczorek May 13 '15 at 05:14