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