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

Validation #313

Closed
LubosBehensky opened this Issue Dec 6, 2017 · 2 comments

Comments

Projects
None yet
2 participants
@LubosBehensky
Copy link

LubosBehensky commented Dec 6, 2017

Hello,

this is not an issue, just a beginner's question. I can´t find any example around.

I would like to use Validation type to collect multiple validation error messages. I am trying it on simple example and I made it work with Either which collects the first error. But I don´t understand how to work with Validation. In my example it does the same as either and doesn´t invoke second test.

        public static void ValidationTests()
        {
            int num = 0;

            //var qry =
            //from res in ValidateNonZero(num)
            //from res1 in ValidateBiggerOne(num)
            //select res1;

            //qry.Match(
            //    Left: e => WriteLine($"error: {e.Message}"),
            //    Right: i => WriteLine($"result: {i.ToString()}"));

            var qry =
            from res in ValidateNonZero(num)
            from res1 in ValidateBiggerOne(num)
            select res1;

            qry.Match(
                Succ: i => WriteLine($"result: {i.ToString()}"),
                Fail: errors => errors.Iter(e => WriteLine($"error: {e.Message}")));
        }

        /* Validation */

        private static Validation<TestError, int> ValidateNonZero(int i)
        {
            return (i == 0 ? Fail<TestError, int>(new TestError("no zero!")) : Success<TestError, int>(i));
        }

        private static Validation<TestError, int> ValidateBiggerOne(int i)
        {
            return (i < 2 ? Fail<TestError, int>(new TestError("must be bigger than 1")) : Success<TestError, int>(i));
        }

        /* Either */
        //private static Either<TestError, int> ValidateNonZero(int i)
        //{
        //    return (i == 0 ? Left<TestError, int>(new TestError("no zero!")) : Right<TestError, int>(i));
        //}

        //private static Either<TestError, int> ValidateBiggerOne(int i)
        //{
        //    return (i < 2 ? Left<TestError, int>(new TestError("must be bigger than 1")) : Right<TestError, int>(i));
        //}
@louthy

This comment has been minimized.

Copy link
Owner

louthy commented Dec 6, 2017

Validation is an applicative as well as a monad. The applicative behaviour allows for collection of multiple failure values, the monad behaviour can only support one failure (so it behaves the same as Either). That's because a series of from statements in LINQ requires each one to be evaluated before moving onto the next one. So:

 var z = from x in FuncThatReturnsX()
         from y in FuncThatReturnsY()
         select x + y;

If FuncThatReturnsX fails then x can't possibly have a value and so if I allowed the monadic expression to continue then the final select x + y would be using undefined values.

With applicatives you can imagine that all the values are being calculated in parallel (they're not, but the point is that each expression isn't dependent on any of the others - whereas clearly the select x+y is dependent on the two from expressions above it).

In example you can do this:

    var z = (FuncThatReturnsX(), FuncThatReturnsY()).Apply((x, y) => x + y);

So in your case you can do this:

public static void ValidationTests()
{
    int num = 0;

    var validNum = (ValidateNonZero(num), ValidateBiggerOne(num)).Apply((_, n) => n);

    validNum.Match(
        Succ: i => WriteLine($"result: {i.ToString()}"),
        Fail: errors => errors.Iter(e => WriteLine($"error: {e.Message}")));
}

static Validation<TestError, int> ValidateNonZero(int i) =>
    i == 0 
        ? Fail<TestError, int>(new TestError("no zero!")) 
        : Success<TestError, int>(i));

static Validation<TestError, int> ValidateBiggerOne(int i) =>
    i > 1
        ? Success<TestError, int>(i))
        : Fail<TestError, int>(new TestError("must be bigger than 1")) ;

Also because ValidateNonZero and ValidateBiggerOne are not transforming the valid result (it's leaving the integer alone), and they're both of the same type, you can do this:

    from _ in ValidateNonZero(num) | ValidateBiggerOne(num)
    select num

Or you could compose them into a reusable operation:

    Validation<TestError, int> ValidateNumber(int num) =>
        ValidateNonZero(num) | ValidateBiggerOne(num);

The | operator will give you all failure values or the first successful value if all terms are successful.

There's some pretty good unit tests for this which shows a real-world-ish example.

@LubosBehensky

This comment has been minimized.

Copy link
Author

LubosBehensky commented Dec 7, 2017

Thank you for the explanation.

Lubo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment