Reputation: 303
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 power
s 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
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