# `ValidationResult` Monad in C#
```csharp
using System.Collections.Immutable;
public record ValidationResult<T, TError>
{
private ValidationResult() { }
private record SuccessCase(T Result) : ValidationResult<T, TError>;
private record ErrorsCase(ImmutableList<TError> _Errors) : ValidationResult<T, TError>;
public static ValidationResult<T, TError> Success(T value) => new SuccessCase(value);
public static ValidationResult<T, TError> Error(TError error) =>
new ErrorsCase(ImmutableList.Create(error));
public static ValidationResult<T, TError> Errors(ImmutableList<TError> errors) =>
new ErrorsCase(errors);
public bool IsSuccess => this is SuccessCase;
public TResult Either<TResult>(
Func<T, TResult> onSuccess,
Func<ImmutableList<TError>, TResult> onError) =>
this switch {
SuccessCase successCase => onSuccess(successCase.Result),
ErrorsCase errors => onError(errors._Errors),
_ => throw new ArgumentOutOfRangeException()
};
public static implicit operator ValidationResult<T, TError>(T v) => Success(v);
public T Force() =>
Either(x => x, _ => throw new("Forced result out of an Error"));
public IImmutableList<TError> ForceError() =>
Either(_ => throw new("Forced error out of a success"), e => e);
}
public static class ValidationResult
{
public static ValidationResult<T2, TError> Apply<T1, T2, TError>(
this ValidationResult<Func<T1, T2>, TError> func,
ValidationResult<T1, TError> arg) =>
func.Either(
f => arg.Either(
a => f(a),
ValidationResult<T2, TError>.Errors),
fErrors => arg.Either(
_ => ValidationResult<T2, TError>.Errors(fErrors),
argErrors => ValidationResult<T2, TError>.Errors(
fErrors.AddRange(argErrors))));
public static ValidationResult<T2, TError> Bind<T1, T2, TError>(
this ValidationResult<T1, TError> m, Func<T1, ValidationResult<T2, TError>> f) =>
m.Either(f, ValidationResult<T2, TError>.Errors);
public static ValidationResult<T2, TError> Map<T1, T2, TError>(
this ValidationResult<T1, TError> m, Func<T1, T2> map) =>
m.Bind<T1, T2, TError>(v => map(v));
public static ValidationResult<T2, TError> Map<T1, T2, TError>(
this Func<T1, T2> map, ValidationResult<T1, TError> m) =>
m.Bind<T1, T2, TError>(v => map(v));
public static ValidationResult<T, TError2> MapErrors<T, TError1, TError2>(
this ValidationResult<T, TError1> m, Func<ImmutableList<TError1>, ImmutableList<TError2>> map) =>
m.Either(
v => v,
e => ValidationResult<T, TError2>.Errors(map(e)));
public static ValidationResult<List<T2>, TError> Traverse<T1, T2, TError>(
this IEnumerable<T1> source,
Func<T1, ValidationResult<T2, TError>> f)
{
var results = new List<T2>();
var errors = ImmutableList<TError>.Empty.ToBuilder();
foreach (var element in source)
{
f(element).Either<object?>(
v =>
{
results.Add(v);
return null;
},
e =>
{
errors.AddRange(e);
return null;
});
}
return errors.Any()
? ValidationResult<List<T2>, TError>.Errors(errors.ToImmutable())
: results;
}
public static ValidationResult<List<T>, TError> Sequence<T, TError>(
this IEnumerable<ValidationResult<T, TError>> source) =>
Traverse(source, x => x);
/// Combines erros if both the left and right validation results are errors.
/// If both are successes, the left value is ignored.
public static ValidationResult<T2, TError> Combine<T1, T2, TError>(
this ValidationResult<T1, TError> left,
ValidationResult<T2, TError> right) =>
left.Either(
_ => right.Either(
r => r,
ValidationResult<T2, TError>.Errors),
leftErrors => right.Either(
_ => ValidationResult<T2, TError>.Errors(leftErrors),
rightErrors => ValidationResult<T2, TError>.Errors(
leftErrors.AddRange(rightErrors))));
public static T DefaultWith<T, TError>(
this ValidationResult<T, TError> m,
Func<ImmutableList<TError>, T> defaultWith) =>
m.Either(x => x, defaultWith);
public static T DefaultValue<T, TError>(
this ValidationResult<T, TError> m, T defaultValue) =>
m.DefaultWith(_ => defaultValue);
/// Used to enable LINQ query expressions. Just a map.
public static ValidationResult<T2, TError> Select<T1, T2, TError>(
this ValidationResult<T1, TError> m, Func<T1, T2> map) =>
m.Map(map);
/// Used to enable LINQ query expressions. Just a bind followed by a map
public static ValidationResult<T3, TError> SelectMany<T1, T2, T3, TError>(
this ValidationResult<T1, TError> m,
Func<T1, ValidationResult<T2, TError>> f,
Func<T1, T2, T3> mapper) =>
m.Bind(t1 => f(t1).Map(t2 => mapper(t1, t2)));
}
```