alexandercannon
alexandercannon

Reputation: 544

How to write point-free functional JS using Ramda

I have an array of objects

const myNumbers = [
  {
   top: 10,
   bottom: 5,
   rate: 5
  },
  {
   top: 9,
   bottom: 4,
   rate: 3
  },
];

I want to run a few functions that make the numbers usable before I do anything with them;

const addTen = r.add(10);
const double = r.multiply(2);

And a function that accepts the numbers and does some maths:

const process = (top, bottom, rate) => r.multiply(r.subtract(bottom, top), rate)

So my final function looks like

muNymbers.map(({ top, bottom, rate }) =>
  process(addTen(top), double(bottom), rate)
);

Just looking at this code you can see both functions are already becoming very nested and not particularly clear. My real problem is slightly more complicated again, and I am struggling to see how I can make this point-free when picking different values for different operations.

Upvotes: 3

Views: 324

Answers (2)

Scott Sauyet
Scott Sauyet

Reputation: 50787

If this is an exercise in writing point-free code, a solution like the one from @JeffreyWesterkamp is fine.

For production code, though, any point-free solution is going to be a lot less readable than this simple version:

const process = ({top, bottom, rate}) => ((2 * bottom) - (10 + top)) * rate;

const myNumbers = [
    { top: 10, bottom: 5, rate: 5 },
    { top: 9, bottom: 4, rate: 3 },
];

console.log(R.map(process, myNumbers));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

The point is to not make a fetish over point-free. It is a tool that sometimes makes code easier to read, easier to understand. But it's not worth using if it doesn't do so.

Don't get me wrong. I'm a big fan of point-free code, and Ramda (disclaimer: I'm one of its authors) has some useful tools to help you write it. But Ramda works just fine with other code. So use it wisely.


One other point (ahem): Ramda supplies two fairly unusual functions, useWith and converge, to make it easier to write point-free code. But converge can often be replaced by the more standard lift. It can't always be replaced, as it has some features for handling variadic functions that lift does not supply, but when you can use lift, I would suggest that you do so. For instance, instead of

converge(subtract, [prop('bottom'), prop('top')])

you might write

lift(subtract)(prop('bottom'), prop('top'))

There's no standard replacement for useWith that I know of. But I would replace converge with lift whenever possible.

Upvotes: 3

JJWesterkamp
JJWesterkamp

Reputation: 7916

Here is a point-free approach. The main functions you're looking for are pipe, evolve and converge. I'm not sure if this is the best way, it's just the best I could think of:

const { add, converge, evolve, map, multiply, pipe, prop, subtract } = R;

const myNumbers = [
    { top: 10, bottom: 5, rate: 5 },
    { top: 9, bottom: 4, rate: 3 },
];

const process = pipe(

    evolve({
        top: add(10),
        bottom: multiply(2),
    }),

    converge(multiply, [

        converge(subtract, [
            prop('bottom'),
            prop('top'),
        ]),

        prop('rate'),
    ]),
);

console.log(map(process, myNumbers));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Upvotes: 4

Related Questions