In the last part of this series, we "upgraded" the Maybe monad to an Either monad in order to add error messages when parsing two integers. To recap, the code using the Either monad via LINQ looked like this:
var somethingOrError = from userID in FSharpOption.TryParseInt(req_userID).ToFSharpChoice("Invalid User ID") from id in FSharpOption.TryParseInt(req_otherID).ToFSharpChoice("Invalid ID") select doSomething(userID, id); somethingOrError.Match(Console.WriteLine, setError);
That code, however, only dealt with setting one error message for the whole computation. When it got the first error, it aborted the rest of the computation.
Sometimes we do want this shortcut evaluation, but more generally, when validating data, we want to validate the whole thing, and then display a list of errors, not abort at the first error we find.
To do this, we need to step down from monads into a weaker abstraction: applicative functors. I've already blogged about applicative functors in F# and C#, mostly in the context of formlets. In a nutshell:
- Applicative functors are defined by two functions: pure and apply.
- These functions must satisfy some laws, which I won't mention here.
- Pure is basically a
Func<T, M<T>>for some M, i.e. it puts some value into M
- Apply is basically a
Func<M<Func<A,B>>,M<A>,M<B>>, i.e. it applies a function within M
- All monads are applicative functors (but not necessarily the other way around).
- All applicative functors are functors (but not necessarily the other way around), which means you can Select() over them with LINQ.
But I don't want to get into much detail here. What's interesting here is that we won't use the "default" implementation of the Either applicative functor (i.e. the one derived from the monad) because it would only keep the first error message, so it would be pretty useless in this case! The Either applicative functor we'll use will of course collect all validation messages in a list.
But first, for comparison, here's some simple imperative code to parse two numbers and then do something with them or display all errors:
var errors = new List<string>(); int userID; var userID_ok = int.TryParse(req_userID, out userID); if (!userID_ok) errors.Add("Invalid user ID"); int id; var id_ok = int.TryParse(req_otherID, out id); if (!id_ok) errors.Add("Invalid ID"); if (errors.Count > 0) setErrors(errors); else Console.WriteLine(doSomething(userID, id));
Just like with CsFormlets, this Either applicative functor can be expressed either "directly", or using LINQ as explained by Tomas Petricek here. In this post I'll skip the direct syntax and use LINQ instead. And using LINQ, the above imperative code is translated into this:
var userID = FSharpOption.TryParseInt(req_userID) .ToFSharpChoice(FSharpList.New("Invalid User ID")); var id = FSharpOption.TryParseInt(req_otherID) .ToFSharpChoice(FSharpList.New("Invalid ID")); var result = from a in userID join b in id on 1 equals 1 select doSomething(a, b); result.Match(Console.WriteLine, setErrors);
Note that it's very similar to the monadic code we were first using, only instead of
from...from... (which compiles to
SelectMany(), i.e. monadic bind) we do
on 1 equals 1" is the syntactic tax we have to pay for bending LINQ like this.
result is of type
FSharpChoice<int, FSharpList<string>> , that is it contains either the result of
doSomething() or a list of errors.
This was a trivial example of using applicative functors for validation; just an introduction. It doesn't show one of its biggest strengths: composability. In the next post we'll look into validating a more complex domain.