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
, R.o
, 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