Reputation: 43579
If I want to compose a series of functions I can use compose
, which will allow me to define a series of functions, with one or more arguments passed into the first, then the return of value of the first passed on to the second and the return value of the second passed on to the third and so on ...
compose(f3, f2, f1)(value);
Which is the equivalent of:
f3(f2(f1(value)))
However, what if I want all three functions to be called with value
?
My use-case is that I have a series of functions which validate a piece of data. In each case the function either throws an error of does nothing if the value is valid. I need to compose these validation functions in a way that passing in a value will result in each function called with the value in sequence. Effectively:
f1(value);
f2(value);
f3(value);
// Do something now we are sure the value is valid.
The only way I can see of doing this using the functions provided by ramda is to use the logic operators to prevent short-circuiting if a value returns false:
const f1 = (v) => console.log(`f1 called with '${v}'`);
const f2 = (v) => console.log(`f2 called with '${v}'`);
const f3 = (v) => console.log(`f3 called with '${v}'`);
const callAll = compose(complement, anyPass);
callAll([f1, f2, f3])('x');
However this feels like a misuse of anyPass
. Is this the most appropriate way of achieving what I want?
Upvotes: 3
Views: 1163
Reputation: 710
However, what if I want all three functions to be called with value?
The function for running several functions on one value is called R.converge.
R.converge(
() => {},
[f1, f2, f3],
)('x');
The second argument is an array of functions. The value (in this case 'x') is thrown into each of them in sequence.
However after that, R.converge takes the return values of f1, f2, f3 and throws them in as arguments to the first argument. You don't need that part of R.converge since you are not doing anything with the return values, so you can just use a function that does nothing.
A callAll can thus be written as:
const callAll =
R.converge(() => {});
which is executed like:
callAll([f1, f2, f3])('x');
or uncurried:
const callAll =
R.uncurryN(2, R.converge(() => {}));
callAll([f1, f2, f3], 'x');
Upvotes: 4
Reputation: 1492
You can keep things simple by having your validation functions return booleans and use reduce
:
// A general purpose, curried validation function
const validate = R.curry((validators, value) =>
R.reduce((acc, fn) => {
return acc === false ? acc : fn(value)
}, true, validators));
const myValidators = [f1, f2, f3]; // Functions return booleans
const myValidator = validate(myValidators);
const isValid = myValidator(myValue); // Returns true or false
Working fiddle here.
Upvotes: 1
Reputation:
You can compose side effects if you treat functions as monads. Here is a vanilla Javascript sketch, but you can express it with Ramda as well:
const chain = f => g => x => f(g(x)) (x);
const of = x => y => x;
const id = x => x;
const yourFun = x => y => y;
const fold = (f, acc) => xs => xs.reduce((acc_, x) => f(acc_) (x), acc);
const comp = f => g => x => f(g(x));
const compn = (...fs) => fold(comp, id) (fs);
const f1 = x => {console.log(`f1 called with ${x}`)}
const f2 = x => {console.log(`f2 called with ${x}`)}
const f3 = x => {console.log(`f3 called with ${x}`)}
compn(
chain(yourFun) (f3),
chain(yourFun) (f2),
chain(yourFun) (f1)
) ("x");
Now yourFun
just ignores the first argument. You probably want to replace that with something more useful for your case.
Please note that this is more of an educational exercise than a proper approach. I wanted to show you how monads can be useful to compose computations with effects.
Upvotes: 1
Reputation: 664196
Effectively:
f1(value); f2(value); f3(value);
Whenever you see a semicolon in your code, you know that you are not programming functionally but are executing a side effect :-)
Don't do that. The easiest way would indeed be to use anyPass
, but have the validation function return booleans instead of throwing an exception.
If you need to get an error message back, you'd use the Either
data type and traverse
:
const f1 = (v) => true ? Either.right("it's fine") : Either.left("oops");
const f2 = (v) => false? Either.right("it's fine") : Either.left("oops");
const f3 = (v) => true ? Either.right("it's fine") : Either.left("oops");
traverse(Either.of, R.apply('x'), [f1, f2, f3]); // Left("oops")
Upvotes: 2