Using functional programming in javascript and typescript together with Ramda, I often find myself writing code like:
const myFun = c => {
const myId = c.id
const value = pipe(
getAnotherOtherPropOfC,
transformToArray,
mapToSomething,
filterSomething,
// ... N other transformations
// ok now I need myId and also the result of the previous function
chainMyIdWithResultOfPreviousFunction(myId)
)(c)
return value
}
Notice how creating a const myId
breaks point-free style. I'd like to write myFun
so that there's no need to explicit c
. So something like:
const myFun = pipe(....)
I was wondering if there's a more functional and readable way of doing things like this.
Can it be done? Sure. Should it be done? It's not so clear.
Here is a point-free version of the above, using
lift
:lift
is a more FP-standard function than Ramda'sconverge
(which together withuseWith
offer ways to make point-free solutions, often at the expense of readability.)lift
overlaps withconverge
when applied to functions, but is designed for unary functions, whereconverge
handles polyadic ones.This is not horrible. But the only advantage it has over the original is that it's point-free. And if you were to try to extend this to intermediate functions in that pipeline, it would get downright ugly.
My take is that point-free can at times lead to cleaner, easier-to-read, and easier-to-maintain code. But there is no reason to go point-free when it doesn't do so.
It's not that point-free is inherently more functional than pointed code. I think this idea starts as a sort of Haskell-envy from other languages. Haskell is held up as an idealized FP language, and it happens to be a language in which point-free comes naturally. But that's at least partially coincidental.
My standard example is that
const sum = reduce(add, 0)
is cleaner and more comprehensible thanconst sum = (xs) => xs.reduce(add, 0)
. It also makes extremely clear the parallels withconst product = reduce(multiply, 1)
orconst all = reduce(and, true)
. But as you get more complex, or when you need to reuse an intermediate calculation (as Bergi pointed out), point-free code often becomes a liability.I don't have a real call here about whether this version is an improvement on the original. If so, it's only a minor one. Carrying it further would significantly degrade the readability.
What about something like this:
Variables are usually an indication that your function is doing too much and should be decomposed. For example, this
can be refactored into two separate functions:
and then simply
which looks good enough to me, but if you want to be absolutely point-free, then
Playground
Please note that I use vanilla JS to reach a wider audiance.
A
pipe
is just a composite function is just a function. So let's use a higher order pipe that takes a pipe, a function and a value and returns another function instead of the result. To actually get the result we need to pass the value twice: