Reputation: 8052
I'm learning functional programming
I have a helper function that composes a function with itself over and over again until some condition is met. Like f(f(f(f(f(f(f(x)))))))
or compose(f,f,f,f,f,f,f)(x)
, except it keeps going unless told to stop.
The way I've implemented it, it doesn't really feel like composition (and perhaps that's the wrong word to use here regardless)
This is my current solution:
const selfComposeWhile = curry(
(pred, fn, init) => {
let prevVal = null;
let nextVal = init;
while(prevVal == null || pred(prevVal, nextVal)){
prevVal = nextVal;
nextVal = fn(nextVal);
}
return nextVal;
}
);
and here it is in use:
const incOrDec = ifElse(gt(30), inc, dec);
console.log(
selfComposeWhile(lt, incOrDec, 0)
); // -> 29
I don't want to use recursion as JavaScript doesn't have proper tail recursion and the namesake of this site (Stack Overflow) is a real concern for how I use this.
There's nothing wrong with it as is, but I've been trying to learn functional programming techniques by applying them to a dummy problem and this is one of the few places my code stands out as decidedly imperative.
I also have
useWith(selfComposeWhile, [pipe(nthArg(1), always)]);
That takes a predicate that is only concerned with the nextVal, which seems like the more general case of this.
Can anybody think of a more functional (sans recursion) way to write selfComposeWhile
and its cousin?
Upvotes: 0
Views: 50
Reputation: 8052
I've taken selfComposeWhile and named it unfoldUntil
. I'm not sure that's the best name as it doesn't return a list. It is basically R.until
where the predicate can access the previous and the next value.
To bring them a bit more in alignment, I've changed my while
behavior into until
behavior (R.complement
the predicate).
If it were typed:
unfoldUntil: <T>(
pred: (p:T, n:T) => boolean,
fn: (a:T) => T,
init: T
) => T
Implemented
const unfoldUntil = curry(
(pred, fn, init) => pipe(
unfold(n =>
isNil(n) ?
false :
call((next = fn(n)) =>
(pred(n, next) ?
[next, null] :
[next, next])
)
),
last
)(init)
);
Notes: This will never pass null/undefined into the transformation function (fn
). You can use a transformation that returns null as a stopping condition and be returned the previous value. Otherwise, you'll be returned the first value of next
that causes the predicate to return true.
Upvotes: 0
Reputation: 192317
R.unfold does mostly what you, it accepts a seed value (init
), and transforms it, and on each iteration it returns the current value, and the new seed value. On each iteration you need to decide to continue or stop using a predicate.
The main difference between your function, and R.unfold is that the last one produces an array, and this is easily solvable with R.last:
const { curry, pipe, unfold, last } = R
const selfComposeWhile = curry(
(pred, fn, init) => pipe(
unfold(n => pred(n, fn(n)) && [n, fn(n)]),
last
)(init)
)
const { ifElse, gt, inc, dec, lt } = R
const incOrDec = ifElse(gt(30), inc, dec)
console.log(selfComposeWhile(lt, incOrDec, 0)) // -> 29
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
Upvotes: 2