question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to harvest errors with validators?

See original GitHub issue

Coming from the great book “Functional Programming in C#”, I try to run a bunch of validators on an object and harvest all the errors that multiple different validators might find.

The book shows a solution where all errors are created by mapping the sequence of validators against their result when invoked with the test object.

So I come up with

List<Error> errors;

The problem now simply is: how do I instantiate my final Validation with this list of errors? I mean, a Validation<Error, T> already knows how to handle a sequence of my Error type!

The Prelude’s helper function though only accepts a single value for error. Why?

So this fails to compile:

{
    var errors = validators
        .Map(validate => validate(t))
        .Bind(v => v.Match(Fail: errs=>Some(errs.Head), Succ: _ => None))
        .ToList();
    return errors.Count == 0
        ? Success<Error, T>(t)
        : Fail(errors.ToSeq());
};

Because Prelude.Fail does only take ONE Error, but not my sequence.

Can anybody explain to me how this might be done with Language Ext? Thanks!

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

7reactions
louthycommented, May 13, 2019

@wawaforya There is already a mechanism to construct a Validation with a sequence of errors:

    Validation<FAIL, SUCCESS>.Fail(Seq<FAIL> fail)

Short circuiting happens when you use bind (i.e. a series of from ... in ... statements). If you think about it, this is necessary, because if I do this:

    from x in validX
    from y in validY
    select x + y;

Then allowing the computation to continue after validX has failed to extract x would lead to operations on undefined state. So, the monadic operation must short cut. This is why the computation to gather all errors is an applicative operation: each term can be computed independently and then brought together for a final operation.

Therefore you need to use Apply. If you look at the ValidationTests.cs unit tests, you’ll see all of these techniques in action:

public static Validation<Error, CreditCard> ValidateCreditCard(string cardHolder, string number, string expMonth, string expYear)
{
    var fakeDateTime = new DateTime(year: 2019, month: 1, day: 1);
    var cardHolderV = ValidateCardHolder(cardHolder);
    var numberV = DigitsOnly(number) | MaxStrLength(16)(number);
    var validToday = ValidExpiration(fakeDateTime.Month, fakeDateTime.Year);

    // This falls back to monadic behaviour because validToday needs both
    // a month and year to continue.  
    var monthYear = from m in ToInt(expMonth).Bind(ValidMonth)
                    from y in ToInt(expYear).Bind(PositiveNumber)
                    from my in validToday(m, y)
                    select my;

    // The items to validate are placed in a tuple, then you call apply to
    // confirm that all items have passed the validation.  If not then all
    // the errors are collected.  If they have passed then the results are
    // passed to the lambda function allowing the creation of a the 
    // CreditCard object.
    return (cardHolderV, numberV, monthYear).Apply((c, num, my) => 
        new CreditCard(c, num, my.month, my.year));
}

cardHolderV, numberV, and monthYear are three Validation monads. They’re grouped into a tuple:

    (cardHolderV, numberV, monthYear)

And then the extension method Apply is run on that tuple. Which, when give a three parameter lambda, can use the success values to generate a new CreditCard value. If any fail, then the lambda doesn’t get called and the errors are aggregated.

You may also notice that the numberV monad is generated by appending two other monads together:

    var numberV = DigitsOnly(number) | MaxStrLength(16)(number);

This can be done when all the monadic types in the expression are the same, and only care about the first successful result being returned. And so this will validate the the number is all digits and that it’s not longer than 16 characters. Again, the errors are aggregated.

monthYear actually makes use of the monad to short-cut if anything fails. So, you can have the best of both worlds.

1reaction
michael-wolfendencommented, Sep 12, 2018

I believe the example was something like

static Func<string, Validation<Error, string>> ShouldBeOfLength(int n) => s =>
    s.Length == n
        ? Success<Error, string>(s)
        : Fail<Error, string>(Error.New(($"{s} should be of length {n}")));

static Func<string, Validation<Error, string>> ShouldBeLowerCase => s =>
    s == s.ToLower()
        ? Success<Error, string>(s)
        : Fail<Error, string>(Error.New($"{s} should be lower case"));

public class Error : NewType<Error, string>
{
    public Error(string value) : base(value) { }
}

If I’m undertstanding you correctly you can do the following

var test = "USA";

(ShouldBeLowerCase(test) | ShouldBeOfLength(2)(test))
    .Apply(value => value)
    .Match(
        Succ: ... // Action<string>
        Fail: ... // Action<Seq<Error>>
    );

Or

var test = "USA";
    
List(ShouldBeLowerCase, ShouldBeOfLength(2))
    .Map(validator => validator(test))
     // this returns a validation that you can match on
    .Sequence()
    .Match(Succ: ..., Fail: ...)

You can also convert the resulting Validation to an Either

var test = "USA";

List(ShouldBeLowerCase, ShouldBeOfLength(2))
    .Map(validator => validator(test))
    .Sequence()
    .ToEither()
    .Match(Right: ..., Left: ...)

You can find more examples at https://github.com/louthy/language-ext/blob/master/LanguageExt.Tests/ValidationTests.cs

Happy to know if there’s a better way though

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to collect all validations errors when using custom ...
I'm using a custom class validator to validate serializer fields, and I would like to raise ValidationError for all fields so API error...
Read more >
Angular validation errors made easy | by Luca Micieli
In Utopia we would just set up validators while creating the FormControls and a language specific message based on the error type would...
Read more >
Displaying Errors
Use errors.collect() without providing a field name to collect all errors into an object which keys are field names and the values are...
Read more >
Form-Field Validation: The Errors-Only Approach
Now, the user simply has to fix the fields shown and hit “Continue” — no scrolling, no having to pick out erroneous fields...
Read more >
Replacing Throwing Exceptions with Notification in Validations
My first step is to split the check method into two parts, an inner part that will eventually deal only with notifications and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found