Closed costa100 closed 5 years ago
Add an example on how to use the ValidationContext and ValidationUnitContext.
Can you add links to those?
It would nice to also show an example where a validation is invoked only if a previous validation passed
Use monadic bind to achieve that behavior. Such an example already exists. See this line in the validation tests to which you linked.
After adding these usings...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LanguageExt;
using static LanguageExt.Prelude;
...I still can't get your code to compile. What are those to calls to Dump()
?
It would nice to also show an example where a validation is invoked only if a previous validation passed
Use monadic bind to achieve that behavior. Such an example already exists. See this line in the validation tests to which you linked.
In fact, you did this here
public static Validation<Error, string> Validators(string str) => NonEmpty(str).Bind(x => StartsWithLetterDigit(str));
Your code contains two validation examples. I have finished going through the first one, the one that starts with
ValidateList(new List<string> {"", "1", "", "a", "200"})
I think there is a key idea that you are missing. The purpose of validation is to guard the creation of strong types.
In the validation tests in Language Ext to which you linked, there is a type called CreditCard
. Its constructor accepts any two string
s and any two int
s. However, having a CreditCard
instance is not the same as having an instance of Tuple<string, string, int, int>
. The difference is that the only call to the constructor of CreditCard
went through many validation steps.
The type safety of CreditCard
is very good. It could be improved a bit using the smart constructor pattern. You can read more about this idea in Functional Programming in C# by Enrico Buonanno. He first mentions the idea in section 3.4.5 and then elaborates on it in section 8.5.1 in the context of validation.
Alternatively, see below how the types I created have private constructors and factory methods involving validation.
using System.Collections.Generic;
using LanguageExt;
using static LanguageExt.Prelude;
public class Example {
public static void Main() {
var strings = new List<string> { "", "1", "", "a", "200" };
var validatedStrings = strings
.Map(Validate)
.Map(v => v.Match(s => s.Value, errors => string.Join(", ", errors))) // just to more easily see what is inside the Validation<,> instances
.ToSeq();
}
public static Validation<Error, AlphaStartingString> Validate(string s) => s
.Apply(NonnullString.New)
.Bind(NonemptyString.New)
.Bind(AlphaStartingString.New);
public sealed class NonnullString : Record<NonnullString> {
public string Value { get; }
private NonnullString(string value) => Value = value;
public static Validation<Error, NonnullString> New(string value) => Optional(value)
.Map(s => new NonnullString(s))
.ToValidation(Error.New("Nonnull string is required"));
public static implicit operator string(NonnullString self) => self.Value;
}
public sealed class NonemptyString : Record<NonemptyString> {
public string Value { get; }
private NonemptyString(string value) => Value = value;
public static Validation<Error, NonemptyString> New(NonnullString nonnull) => Some(nonnull)
.Filter(s => !string.IsNullOrEmpty(s))
.Map(s => new NonemptyString(s))
.ToValidation(Error.New("Nonempty string is required"));
public static implicit operator string(NonemptyString self) => self.Value;
}
public sealed class AlphaStartingString : Record<AlphaStartingString> {
public string Value { get; }
private AlphaStartingString(string value) => Value = value;
public static Validation<Error, AlphaStartingString> New(NonemptyString nonempty) => Some(nonempty)
.Filter(s => s.Value[0].Apply(char.IsLetter))
.Map(s => new AlphaStartingString(s))
.ToValidation(Error.New($"{nonempty.Value} doesn't start with a letter"));
public static implicit operator string(AlphaStartingString self) => self.Value;
}
public class Error : NewType<Error, string> {
public Error(string e) : base(e) { }
}
}
Add an example on how to use the ValidationContext and ValidationUnitContext.
Can you add links to those?
I found ValidationContext
and ValidationUnitContext
.
The purpose of ValidationContext
(respectively ValidationUnitContext
) is just to pass the two arguments to Match
(respectively Match
) one at a time.
For example, instead of
public static void Main() {
var strings = new List<string> { "", "1", "", "a", "200" };
var validatedStrings = strings
.Map(Validate)
.Map(v => v
.Match(s => s.Value, errors => string.Join(", ", errors))) // just to more easily see what is inside the validation instances
.ToSeq();
}
you can write
public static void Main() {
var strings = new List<string> { "", "1", "", "a", "200" };
var validatedStrings = strings
.Map(Validate)
.Map(v => v
.Succ(s => s.Value)
.Fail(errors => string.Join(", ", errors))) // just to more easily see what is inside the validation instances
.ToSeq();
}
For your second validation example, the one that starts with
ValidatorsParent(parent)
do you think you can improve it based on my review of your first example?
Thank you for taking the time to review the code and for the comments!
Re: Dump() it is a function implemented in linqpad. I used linqpad to test the code. The using blocks are added in a separate dialog in linqpad, that's why they are missing from the code - sorry for the confusion.
Yes, I started to read the Functional Programming in C# book. Just a side comment, I wish he used your library instead of writing his own code - it makes it harder when you want to adopt the techniques and use the FP style, the concepts are similar but the code is not exactly the same.
I think there is a key idea that you are missing. The purpose of validation is to guard the creation of strong types.
You make a very good point. Currently, my code is mostly imperative, however, I was looking at the validation classes to implement validation for configuration classes that receive values from the app.config file. My thought was to gradually introduce the FP style in the code. Validation is good start, I think.
do you think you can improve it based on my review of your first example?
Yes, your examples, NonnullString, NonemptyString etc. are very good. I can take it from here.
Thanks again
Hi,
This is not an issue with the library, it is a request for examples on how to use validation. I already read the current validation tests, however, I feel it's not enough for a beginner in the FP arts :-). Particularly:
Add an example on how to use the ValidationContext and ValidationUnitContext. I am interested in passing additional data to the validators. Sometimes it is necessary to query databases to validate some piece of data.
Add examples on how to combine the results from validating child classes and parent classes. It would nice to also show an example where a validation is invoked only if a previous validation passed (perhaps add a & operator - I used bind below). I played with this stuff and here is some code that I ran in linqpad.
Overall I got it working, however, I get the feeling it can be improved.
Thanks