Open OliverJAsh opened 7 years ago
This library has grown without much of a plan. If you're interested in creating one, I'm sure people would be interested. If you're looking for others who might be able to build one, you'll have to pique their interests; it would probably help to explain why you think this would be helpful.
Personally I have found that using Either's disjunction nature you can implement validations with ease. The difference will mainly lie in the wording used for success/failure (left, right). Given its a monad and that it implements the bifunctor spec you have a lot of flexibility as to how you manage the two possible paths.
I haven't used Validation
except in fiddling around, but my understanding is that it has one significant advantage over Either
in that it collects multiple failure messages together. ("password too short" / "password must contain digits") Either
doesn't allow this, correct?
For such a use case you could make an Either of a Tuple where you store the data to be validated and a validation state array where you can store the errors. Upon Left, add error to the array while keeping the value, upon right, pass the previous input along (or viceversa, which ever rocks your boat). Then you'd just pass a checker function configured with a condition to both sides with a bimap call.
There will be other ways do this of course. Any ideas?
@ferborva Is this what you're talking about? Note I've had to do this using Folktale instead of ramda-fantasy, because this library doesn't expose a Either.fold
method:
const Either = require('data.either')
// Either<X, Y>[] => Either<X[], Y[]>
const sequenceEithers = eithers => (
eithers
.reduce((acc, either) => (
acc.fold(
accLeft => either.fold(
left => Either.Left(accLeft.concat(left)),
right => Either.Left(accLeft)
),
accRight => either.fold(
left => Either.Left([left]),
right => Either.Right(accRight.concat(right))
)
)
), Either.Right([]))
)
{
const eithers = [
Either.Left(1),
Either.Left(2),
]
console.log(sequenceEithers(eithers))
}
{
const eithers = [
Either.Right(1),
Either.Right(2),
]
console.log(sequenceEithers(eithers))
}
{
const eithers = [
Either.Left(1),
Either.Left(2),
Either.Right(3),
Either.Right(4),
]
console.log(sequenceEithers(eithers))
}
Morning @OliverJAsh. I was thinking more a long the lines of something like this:
const Either = require('ramda-fantasy').Either;
const Identity = require('ramda-fantasy').Identity;
const Tuple = require('ramda-fantasy').Tuple;
const R = require('ramda');
// Build a checker function
const checker = (predicate, errorMsg, tuple) => {
if (predicate(Tuple.fst(tuple))) {
// The value is valid
} else {
// The value is not valid => Add error msg to the array
tuple.map(arr => arr.push(errorMsg));
}
return tuple;
};
// Curry the checker
const curriedChecker = R.curry(checker);
// Validation predicate example
const isGreaterThan40 = x => x > 40;
// Is Number + Msg
const myIsNumber = curriedChecker(R.is(Number), 'Value must be a number');
// Is greater that 40 + Msg
const myGreaterThan40 = curriedChecker(isGreaterThan40, 'Value must be greater than 40');
/*
To validate a value we put it into a Tuple together with an empty array
We build a validator function which takes the value and passes
it through all our checkers
This could very well be extracted to function that
takes a value and an array of predicates
*/
const myNumberValidator = (x) => {
const checkedTuple = Identity.of(Tuple(x, []))
.map(myIsNumber)
.map(myGreaterThan40)
.chain(R.identity);
if (Tuple.snd(checkedTuple).length) {
// There are errors
return Either.Left(checkedTuple);
}
return Either.of(checkedTuple);
};
const allGood = 42;
const badValue = 2;
const badNumberAndValue = 'foo';
console.log(myNumberValidator(allGood));
// _Right { value: _Tuple { '0': 42, '1': [], length: 2 } }
console.log(myNumberValidator(badValue));
// _Left { value: _Tuple { '0': 2, '1': [ 'Value must be greater than 40' ], length: 2 } }
console.log(myNumberValidator(badNumberAndValue));
// _Left {
// value:
// _Tuple {
// '0': 'foo',
// '1': [ 'Value must be a number', 'Value must be greater than 40' ],
// length: 2 } }
You put the value into a Tuple inside an Identity container in order to map predicate validations while you accumulate the errors in the second position of the Tuple. Once that is done you can return the checkedTuple as is or check the Tuple of errors and place it into a Left or a Right.
In case you put the Tuple into an Either you can use 'either' static to manage the outcome:
Are there any plans to add a Validation type, like in Folktale? docs.folktalejs.org/en/latest/api/data/validation/Validation.html