简体   繁体   中英

Making something point-free in Ramda

I'm learning about the JS Library Ramda rightnow. And there seems to be a pattern i am unable to resolve properly. For example i have this code.

const filterPayments = filter => payments =>
  r.filter(
    r.allPass([
      typePred(filter),
      amountPred(filter),
      accountPred(filter),
      currencyPred(filter)
    ]),
    payments
  );

Now this seems to be a case where i'm not to far off beeing able to make it point free. I just can't seem to find the right way.

In addition i have troubles making logical functions like this completely functional and Point-free:

const amountPred = filter => p =>
  r.and(p.betrag >= filter.amount.min, p.betrag <= filter.amount.max);

Maybe somebody can point me into the right direction or offer some guidance?

The guidance I'd offer is to stick with what you currently have :)

I can show you an example of what point-free translations of what you have there could look like, though the resulting code is quite horrible to read and is really only good for a session of mental gymnastics.

First up, payments appears in the final position of both sides of the function expression so we can simply drop that.

const filterPayments = filter =>
  R.filter(
    R.allPass([
      typePred(filter),
      amountPred(filter),
      accountPred(filter),
      currencyPred(filter)
    ]));

Next we can look to deal with the list of functions passed to R.allPass

R.allPass(R.map(p => p(filter), [typePred, amountPred, accountPred, currencyPred]))

Now we can begin to remove the filter argument by flipping R.map to push it towards the end.

R.allPass(R.flip(R.map)([typePred, amountPred, accountPred, currencyPred])(p => p(filter))

Now lets make filter and argument to p => p(filter) instead of closing over it, making it:

R.allPass(R.flip(R.map)
  ([typePred, amountPred, accountPred, currencyPred])
  (R.applyTo(filter)))

This is now starting to take shape like h(g(f(a))) which we can transform to point-free using R.pipe , R.compose , Ro , etc.

const filterPayments = R.compose(
  R.filter,
  R.allPass,
  R.flip(R.map)([typePred, amountPred__, accountPred, currencyPred]),
  R.unary(R.applyTo) // R.unary is needed here to tell Ramda only to expect the first argument in this composition
)

This gives us a function that should now be equivalent to your filterPayments in point-free form.

Your amountPred example gets even more convoluted and I think you'll agree afterwards that what you already have is a much better choice between the two.

Again, we start by trying to push arguments to the end of each side of the expression:

filter => p =>
  both(
    x => x >= filter.amount.min,
    x => x <= filter.amount.max
  )(p.betrag)

And then we can drop the p :

filter => R.o(
  both(
    x => x >= filter.amount.min,
    x => x <= filter.amount.max
  ),
  prop('betrag'))

The functions passed to both can also be swapped out:

filter => R.o(
  both(
    R.flip(R.gte)(filter.amount.min),
    R.flip(R.lte)(filter.amount.max)
  ),
  prop('betrag'))

Now to tackle filter , we can use R.converge to pass the same argument to multiple functions and then combine the results of each together.

filter => R.o(
  R.converge(both, [
    f => R.flip(R.gte)(R.path(['amount', 'min'], f),
    f => R.flip(R.lte)(R.path(['amount', 'max'], f)
  ])(filter),
  prop('betrag'))

And now the f is in the end position, so it can be dropped by composition:

filter => R.o(
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])(filter),
  prop('betrag'))

Getting filter to the end is where it gets quite confusing, involving flipping composition functions.

filter => R.o(
  R.flip(R.o)(R.prop('betrag')),
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])
)(filter)

And finally...

const amountPred = R.o(
  R.flip(R.o)(R.prop('betrag')),
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])
)

... comparing that to your original, I know which I prefer to read:

const amountPred = filter => p =>
  p.betrag >= filter.amount.min && p.betrag <= filter.amount.max

I had started to do the same sort of work that Scott Christopher did, and refactored the main function to point-free, knowing from the start that my advice would almost certain to be to not bother with point-free for this. I found my point-free version, which was simpler than I expected. Before I started on the predicates, I looked to see Scott's solution.

He did much what I would have done, and probably better than I would have. And of course he ends with similar advice. I always try to suggest that people don't make a fetish out of point-free. Use it when it makes your code cleaner and more understandable. Don't use it when it doesn't.

I wouldn't answer at all, except that I ended up with a slightly nicer point-free version of the main function, and would like to offer that up for consideration:

const filterPayments = pipe(
  juxt([typePred, amountPred, accountPred, currencyPred]),
  allPass,
  filter
)

Although this is fairly clean, I don't think it's as readable as your original, and so I wouldn't recommend it. But if you really want point-free, this is a possibility.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM