# `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)));
}
```