Functional programming: Return first truthy result of calling a list of different functions with a specific argument
While Ramda's anyPass
is similar in spirit, it simply returns a boolean if any of the functions yield true. Ramda (disclaimer: I'm a Ramda author) does not have this exact function. If you think it belongs in Ramda, please feel free to raise an issue or create a pull request for it. We can't promise that it would be accepted, but we can promise a fair hearing.
Scott Christopher demonstrated what is probably the cleanest Ramda solution.
One suggestion that hasn't been made yet is a simple recursive version, (although Scott Christopher's lazyReduce
is some sort of kin.) Here is one technique:
const firstTruthy = ([fn, ...fns], ...args) =>
fn == undefined
? null
: fn (...args) || firstTruthy (fns, ...args)
const functions = [
(input) => input % 3 === 0 ? 'multiple of 3' : false,
(input) => input * 2 === 8 ? 'times 2 equals 8' : false,
(input) => input + 2 === 10 ? 'two less than 10' : false
]
console .log (firstTruthy (functions, 3)) // 'multiple of 3'
console .log (firstTruthy (functions, 4)) // 'times 2 equals 8'
console .log (firstTruthy (functions, 8)) // 'two less than 10'
console .log (firstTruthy (functions, 10)) // null
I would probably choose to curry the function, either with Ramda's curry
or manually like this:
const firstTruthy = ([fn, ...fns]) => (...args) =>
fn == undefined
? null
: fn (...args) || firstTruthy (fns) (...args)
// ...
const foo = firstTruthy (functions);
[3, 4, 8, 10] .map (foo) //=> ["multiple of 3", "times 2 equals 8", "two less than 10", null]
Alternatively, I might use this version:
const firstTruthy = (fns, ...args) => fns.reduce((a, f) => a || f(...args), null)
(or again a curried version of it) which is very similar to the answer from Matt Terski, except that the functions here can have multiple arguments. Note that there is a subtle difference. In the original and the answer above, the result of no match is null
. Here it is the result of the last function if none of the other were truthy. I imagine this is a minor concern, and we could always fix it up by adding a || null
phrase to the end.
You could use Array#some
with a short circuit on a truthy value.
const
firstTruthy = (functions, data) => {
let result;
functions.some(fn => result = fn(data));
return result || null;
},
functions = [
input => input % 3 === 0 ? 'multiple of 3' : false,
input => input * 2 === 8 ? 'times 2 equals 8' : false,
input => input + 2 === 10 ? 'two less than 10' : false
];
console.log(firstTruthy(functions, 3)); // 'multiple of 3'
console.log(firstTruthy(functions, 4)); // 'times 2 equals 8'
console.log(firstTruthy(functions, 8)); // 'two less than 10'
console.log(firstTruthy(functions, 10)); // null
Ramda has a way of short-circuiting R.reduce
(and a couple of others) using the R.reduced
function to indicate that it should stop iterating through the list. This not only avoids applying further functions in the list, but also short-circuits iterating further through the list itself which can be useful if the list you are working with is potentially large.
const firstTruthy = (fns, value) =>
R.reduce((acc, nextFn) => {
const nextVal = nextFn(value)
return nextVal ? R.reduced(nextVal) : acc
}, null, fns)
const functions = [
(input) => input % 3 === 0 ? 'multiple of 3' : false,
(input) => input * 2 === 8 ? 'times 2 equals 8' : false,
(input) => input + 2 === 10 ? 'two less than 10' : false
]
console.log(
firstTruthy(functions, 3), // 'multiple of 3'
firstTruthy(functions, 4), // 'times 2 equals 8'
firstTruthy(functions, 8), // 'two less than 10'
firstTruthy(functions, 10) // null
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>
An alternative option is to create a "lazy" version of reduce
which only continues if you apply the function passed as the accumulated value which continues iterating recursively through the list. This gives you control inside the reducing function to short-circuit by not applying the function that evaluates the rest of the values in the list.
const lazyReduce = (fn, emptyVal, list) =>
list.length > 0
? fn(list[0], () => lazyReduce(fn, emptyVal, list.slice(1)))
: emptyVal
const firstTruthy = (fns, value) =>
lazyReduce((nextFn, rest) => nextFn(value) || rest(), null, fns)
const functions = [
(input) => input % 3 === 0 ? 'multiple of 3' : false,
(input) => input * 2 === 8 ? 'times 2 equals 8' : false,
(input) => input + 2 === 10 ? 'two less than 10' : false
]
console.log(
firstTruthy(functions, 3), // 'multiple of 3'
firstTruthy(functions, 4), // 'times 2 equals 8'
firstTruthy(functions, 8), // 'two less than 10'
firstTruthy(functions, 10) // null
)