Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plans to add Validation type #152

Open
OliverJAsh opened this issue Feb 27, 2017 · 6 comments
Open

Plans to add Validation type #152

OliverJAsh opened this issue Feb 27, 2017 · 6 comments

Comments

@OliverJAsh
Copy link

Are there any plans to add a Validation type, like in Folktale? docs.folktalejs.org/en/latest/api/data/validation/Validation.html

@CrossEye
Copy link
Member

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.

@ferborva
Copy link

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.

@CrossEye
Copy link
Member

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?

@ferborva
Copy link

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?

@OliverJAsh
Copy link
Author

@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))
}

@ferborva
Copy link

ferborva commented Mar 5, 2017

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:

  • Either.either(handleError, handleSuccess, returnedEither)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants