kibe
kibe

Reputation: 303

"Undefined" when trying to reproduce a functional pipe in Javascript

I just started studying functional programming today and I am trying to create an example using a pipe:

const pipe = (...fns) => x => fns.reduce((acc, f) => f(acc), x)
const buffs = [{ power: 5, type: 'SPEED' }, { power: 2, type: 'SPEED' }]
let pos = { x: 0, y: 0 }

const addToPos = pos => amount => ({ ...pos, x: pos.x + amount })
const add1ToPos = pos => addToPos(pos)(1)
const add2ToPos = pos => addToPos(pos)(2)
const addAll = pos => pipe(
  add1ToPos,
  add2ToPos,
)(pos)

pos = addAll(pos)
console.log(pos) // returns { x: 3, y: 0 } as expected

However, when I try to add all these powers from buffs inside my pipe, like so:

const addAll = pos => pipe(
  add1ToPos,
  add2ToPos,
  addToPos(buffs.reduce((a, b) => a + b.power, 0))
)(pos)

I get

{ x: undefined[object Object] }

I really don't know why this is happening since this works perfectly:

const numbers = [{ num: 4, color: 'blue' }, { num: 5, color: 'red' }]
let total = 0

const addToTotal = total => amount => total + amount
const add1ToTotal = total => addToTotal(total)(1)

const addAll = total => pipe(
  add1ToTotal,
  addToTotal(numbers.reduce((a, b) => a + b.num, 0))
)(total)

total = addAll(total) // returns 10 as expected

What am I doing wrong?

Upvotes: 1

Views: 129

Answers (1)

Ben Stephens
Ben Stephens

Reputation: 3371

I'm new to this kind of thing too, but I was wondering, why not rewrite const addToPos = pos => amount => ({ ...pos, x: pos.x + amount }) to const addToPos = amount => pos => ({ ...pos, x: pos.x + amount })

const pipe = (...fns) => x => fns.reduce((acc, f) => f(acc), x)
const buffs = [{ power: 5, type: 'SPEED' }, { power: 2, type: 'SPEED' }]
let pos = { x: 0, y: 0 }

const addToPos = amount => pos => ({ ...pos, x: pos.x + amount })
const add1ToPos = addToPos(1)
const add2ToPos = addToPos(2)
const addAll = pos => pipe(
  add1ToPos,
  add2ToPos,
  addToPos(buffs.reduce((a, b) => a + b.power, 0)),
)(pos)

pos = addAll(pos)
console.log(pos)

An attempt at a more detailed explanation of why the last example's addAll works but the first example's does not (sorry if it's a bit verbose):

So to start out, the last example's addAll, is sort of wrong. Let's look at addToTotal:

const addToTotal = total => amount => total + amount

If you call addToTotal(4) what you have is a new function that looks something like amount => 4 + amount, notice that here we've allocated 4 to the total argument rather than the amount argument, when what we probably wanted was to be left with total => total + 4 as the pipe operations are trying to add values to the total. So why does it work? The order of operations for addition don't matter and amount => 4 + amount and total => total + 4 will always produce the same result.

So why does the first example not work?

In the first example addAll is passing a pos object to each function in the pipe. The last function is addToPos(buffs.reduce((a, b) => a + b.power, 0)), what is addToPos(buffs.reduce((a, b) => a + b.power, 0))? Well if we imagine that buffs.reduce((a, b) => a + b.power, 0) is 5 it should be equivalent to:

addToPos(5)

which will be:

amount => ({ ...5, x: 5.x + amount })

when really what we want to have is a function that takes pos as an argument; something like:

pos => ({ ...pos, x: pos.x + 5 })

If we swap pos and amount in the call chain (not sure if that's the right term) we get:

addToPos = amount => pos => ({ ...pos, x: pos.x + amount })

If we apply an amount to that, we're left with a function that takes the pos object as an argument.

It might make it easier to see these things if the functions are written out long hand; original addToPos:

function addToPos(pos) {
  return function(amount) {
    return { ...pos, x: pos.x + amount };
  }
}

addToPos that returns a function that takes pos as an argument:

function addToPos(amount) {
  return function(pos) {
    return { ...pos, x: pos.x + amount };
  }
}

Upvotes: 3

Related Questions