Luke
Luke

Reputation: 527

Compose - functional programming

I wanted to create my own implementation of compose

This works

const compose = (...fns) => fns.reduce((f,g) => (...args) => f(g(...args)));

This not

const compose1 = (...fns) => (...args) => fns.reduce((f,g) => f(g(...args)));

.

const multi = a => x => x*a;
const devide = a => x => x/a;
const add = a => x => x+a;

compose(add(6), multi(4), devide(5))(5); // 10 as expected

compose1(add(6), multi(4), devide(5))(5); // Uncaught TypeError: f is not a function

Could you explain me why second implementation doesn't work?

Upvotes: 2

Views: 197

Answers (4)

Prasanna
Prasanna

Reputation: 4646

const compose = (...fns) => {
  console.log('function', fns);
  return fns.reduce((f,g) => {
    console.log('f and g' , f,g)
    return (...args) => {
      console.log('args is ' , args)
      return f(g(...args));
    }
  })
};

// call with 
compose(add(6), multi(4), devide(5))(5);

will yield

VM1239:2 function (3) [ƒ, ƒ, ƒ]
VM1239:4 f and g x => x+a x => x*a
VM1239:4 f and g (...args) => {
      console.log('args is ' , args)
      return f(g(...args));
    } x => x/a
VM1239:6 args is  [5]
VM1239:6 args is  [1]
10

Which works all fine do the same with your compose1

const compose1 = (...fns) => {
  console.log('functions ', fns)
  return (...args) => {
    console.log('args', args)
    return fns.reduce((f,g) => {
      console.log('f and g', f,g)
      return f(g(...args))
    })
  }
};
//called with 
compose1(add(6), multi(4), devide(5))(5)

will yield

VM1783:2 functions  (3) [ƒ, ƒ, ƒ]
 0: x => x+a
 1: x => x*a
 2: x => x/a
VM1783:4 args [5]
VM1783:6 f and g x => x+a x => x*a
VM1783:6 f and g 26 x => x/a
VM1783:7 Uncaught TypeError: f is not a function
    at fns.reduce (<anonymous>:7:14)
    at Array.reduce (<anonymous>)
    at args (<anonymous>:5:16)
    at <anonymous>:1:38

so your f is now 26 which is not a function :)

Explanation:

you called 
f (g (...args)) 
f ( g ( 5 ) )
f ( 5 * a)  // a is 4
f ( 20 )
16 + a //  a is 6
26

Upvotes: 2

Eric Ferreira
Eric Ferreira

Reputation: 1949

Let's take a look at your compose1 by breaking it down:

const compose1 = (...fns) => {
    // Returns a function that reduces fns each time it is called        
    return (...args) => {
        // Returns the reduction of fns
        return fns.reduce((f,g) => {
            // Returns the result of calling f(g(...args))
            return f(g(...args))
        })
    }
};

Now lets go through a sample call of this version of compose:

compose1(add(6), multi(4), devide(5))(5);

First, we call compose1. This returns this function:

(...args) => {
    // Returns the reduction of fns
    return fns.reduce((f,g) => {
        // Returns the result of calling f(g(...args))
        return f(g(...args))
    })
}

We then call this function with a parameter of 5, which is essentially calling fns.reduce as in the previously returned function. To see what exactly is going wrong, let's run through a few iterations of the reduce:

First iteration:

fns.reduce((f = add(6), g = multi(4)) => {
    // Returns the result of calling f(g(...args))
    return f(g(...args = [5]))
    // g is `multi(4)`, so do `multi(4)(5)`, which returns 20. Next:
    // f is `add(6)`, so do `add(6)(20)`, which returns 26.
    // Remember that the return from last iteration is
    //   the first parameter of the next iteration
})

Second iteration:

fns.reduce((f = 26, g = devide(5)) => {
    // Returns the result of calling f(g(...args))
    return f(g(...args = [5]))
    // g is `devide(5)`, so do `devide(5)(5)`, which returns 1. Next:
    // f is 26, so do `26(1)`. However, 26 is not a function.
    // You get the error `f is not a function`.
})

Upvotes: 1

smac89
smac89

Reputation: 43186

This is what you needed to to do make your version work:

const compose1 = (...fns) => (...args) => fns.reduceRight((res, f) => f(res), fns.pop()(...args))

The reason why yours failed is because reduce (and reduceRight) takes a function with this signature:

(previousValue, currentValue) => newPreviousValue

Explanation:

What happens in your version is that after the first time, reduce calls your function, newPreviousValue is now no longer a function, it is now f(g(...args)) (the result of the last function call), so when reduce tries to "call" that function again, it uses the result of the last call as if it's a function, so you get the error.

If you see the one I have above, res is the previousValue, which is the result of the last function call, but it is not being used as a function.

Further

If you are wondering how come it even succeeded on the first call, well the reason for this is because you did not supply an initial value for reduce, so it simply took the first two functions from the list as the parameters for the function, which allowed you to do: f(g(...args))

Upvotes: 2

jmcgriz
jmcgriz

Reputation: 3368

By expanding the syntax out into multiple lines, you can start to see where the problems are in the second function. You're callings fns.reduce where you should be calling args.reduce. From there, it still returns a different value so there's most likely some other issues going on, but that's a start.

The console logs in the second should also show that the value of f isn't what you're expecting it to be.

Ideally you should only write shorthand for very simple operations, or for things you've already fully tested out in a manner easier to read and test against.

const compose = (...fns) => {
  return fns.reduce((f,g) => {
    console.log('f: ',f,'g: ',g)
    return (...args) => f(g(...args))
  })
 };

const compose1 = (...fns) => {
  return (...args) => {
    return args.reduce((f,g) => { //was fns.reduce
      console.log('f: ',f,'g: ',g)
      return f(g(...args))
    })
  }
}

const multi = a => x => x*a;
const devide = a => x => x/a;
const add = a => x => x+a;

var result1 = compose(add(6), multi(4), devide(5))(5); // 10 as expected
console.log(result1)
var result2 = compose1(add(6), multi(4), devide(5))(5); // Uncaught TypeError: f is not a function
console.log(result2)

Upvotes: 1

Related Questions