Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional way of re-using variables in a pipe

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.

like image 222
maxpower90 Avatar asked Nov 01 '25 14:11

maxpower90


1 Answers

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:

const getAnotherOtherPropOfC = prop ('x')
const transformToArray = split (/,\s*/)
const mapToSomething = map (Number)
const filterSomething = filter (n => n % 2 == 1)
const square = (n) => n * n
const chainMyIdWithResultOfPreviousFunction = (id) => (arr) => `${id}: [${arr}]`

const myFun = lift (chainMyIdWithResultOfPreviousFunction) (
  prop ('id'),
  pipe(
    getAnotherOtherPropOfC,
    transformToArray,
    mapToSomething,
    filterSomething,
    map (square)
  )
)

console .log (
  myFun ({id: 'foo', x: '1, 2, 3, 4, 5'}) // => 'foo: [1,9,25]'
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const {prop, split, map, filter, lift, pipe} = R            </script>

lift is a more FP-standard function than Ramda's converge (which together with useWith offer ways to make point-free solutions, often at the expense of readability.) lift overlaps with converge when applied to functions, but is designed for unary functions, where converge 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 than const sum = (xs) => xs.reduce(add, 0). It also makes extremely clear the parallels with const product = reduce(multiply, 1) or const 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.

like image 115
Scott Sauyet Avatar answered Nov 04 '25 05:11

Scott Sauyet