Skip to content

Instantly share code, notes, and snippets.

@mausch
Created October 1, 2013 06:37
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mausch/6774627 to your computer and use it in GitHub Desktop.
Save mausch/6774627 to your computer and use it in GitHub Desktop.
Applicative validation is simple.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
// As opposed to magic validation libraries that rely on reflection and attributes,
// applicative validation is pure, total, composable, type-safe, works with immutable types, and it's easy to implement.
public abstract class Result<T> {
private Result() { }
private class ErrorResult<A> : Result<A> {
private readonly IReadOnlyDictionary<string, string> ErrorsDict; // I chose a dictionary here to associate form name with its error, but you could use any semigroup.
public ErrorResult(IReadOnlyDictionary<string, string> errorsDict) {
ErrorsDict = errorsDict;
}
public override R Match<R>(Func<A, R> success, Func<IReadOnlyDictionary<string, string>, R> errors) {
return errors(ErrorsDict);
}
}
private class SuccessResult<A> : Result<A> {
private readonly A Value;
public SuccessResult(A value) {
Value = value;
}
public override R Match<R>(Func<A, R> success, Func<IReadOnlyDictionary<string, string>, R> errors) {
return success(Value);
}
}
public static Result<T> Success(T value) {
return new SuccessResult<T>(value);
}
public static Result<T> Errors(IReadOnlyDictionary<string, string> errors) {
return new ErrorResult<T>(errors);
}
public abstract R Match<R>(Func<T, R> success, Func<IReadOnlyDictionary<string, string>, R> errors);
}
public static class Result {
public static Result<T> Success<T>(T value) {
return Result<T>.Success(value);
}
public static Result<T> Errors<T>(IReadOnlyDictionary<string, string> errors) {
return Result<T>.Errors(errors);
}
public static Result<T> Error<T>(string name, string msg) {
return Errors<T>(new Dictionary<string, string> {{name, msg}});
}
public static Result<R> Select<A,R>(this Result<A> value, Func<A, R> map) {
return value.Match(success: v => Success(map(v)), errors: Errors<R>);
}
static IReadOnlyDictionary<K,V> Union<K,V>(this IReadOnlyDictionary<K,V> a, IReadOnlyDictionary<K,V> b) {
var r = new Dictionary<K, V>();
foreach (var d in new[] {a,b})
foreach (var kv in d)
r[kv.Key] = kv.Value;
return r;
}
public static Result<R> SelectMany<A,B,R>(this Result<A> value, Func<Result<A>, Result<B>> ap, Func<A,B,R> map) {
var valueB = ap(value);
return value.Match(
success: a => valueB.Match(
success: b => Success(map(a, b)),
errors: Errors<R>),
errors: e => valueB.Match(
success: _ => Errors<R>(e),
errors: e2 => Errors<R>(e.Union(e2))));
}
}
// Model to validate & "hydrate"/bind from submitted form
class Model {
public readonly string Name;
public readonly int Age;
public Model(string name, int age) {
Name = name;
Age = age;
}
public override string ToString() {
return string.Format("Name: {0}, Age: {1}", Name, Age);
}
}
class Program {
// example
static Result<int> ValidateInt(string name, string value) {
int v;
if (int.TryParse(value, out v))
return Result.Success(v);
return Result.Error<int>(name, "Invalid number");
}
static Result<string> ValidateName(NameValueCollection form) {
if (string.IsNullOrEmpty(form[FormName]))
return Result.Error<string>(FormName, "Name is required");
return Result.Success(form[FormName]);
}
const string FormName = "name";
const string FormAge = "age";
static Result<Model> Validate(NameValueCollection form) {
return
from name in ValidateName(form)
from age in ValidateInt(FormAge, form[FormAge])
select new Model(name, age);
}
static void Main(string[] args) {
var form = new NameValueCollection {
{FormAge, "abc"},
{FormName, ""},
};
Validate(form)
.Match(
success: model => {
Console.WriteLine(model);
return 0;
},
errors: e => {
foreach (var kv in e)
Console.WriteLine("Error in form element {0}: {1}", kv.Key, kv.Value);
return 0;
});
}
}
@mausch
Copy link
Author

mausch commented Oct 1, 2013

More examples and links about applicative validation: http://bugsquash.blogspot.com/2012/03/example-of-applicative-validation-in.html

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