Last active
February 15, 2024 13:00
-
-
Save flensrocker/49cadca3fa422c9cf3b5997d5e1915b5 to your computer and use it in GitHub Desktop.
Either monad in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Either<Exception, string?>[] eithers = | |
[ | |
new Either<Exception, string?>.Right("Text"), | |
new Either<Exception, string?>.Right(null), | |
new Either<Exception, string?>.Left(new Exception("Oops!")), | |
]; | |
var TryStringLength = EitherExtensions.TryInvoke<string?, int>(LegacyFunctions.StringLength); | |
foreach (var either in eithers) | |
{ | |
var result = either | |
.SelectMany(TryStringLength) | |
.Match( | |
ex => $"Error: {ex.Message}", | |
len => $"Length is {len}" | |
); | |
// Oder mit LINQ: | |
// var result = (from e in either | |
// from r in TryStringLength(e) | |
// select r) | |
// .Match( | |
// ex => $"Error: {ex.Message}", | |
// len => $"Length is {len}" | |
// ); | |
Console.WriteLine(result); | |
} | |
// Output: | |
// Length is 4 | |
// Error: Value cannot be null. (Parameter 'text') | |
// Error: Oops! | |
public static class LegacyFunctions | |
{ | |
public static int StringLength(this string? text) | |
{ | |
ArgumentNullException.ThrowIfNull(text); | |
return text.Length; | |
} | |
} | |
public abstract class Either<TLeft, TRight> | |
{ | |
private Either() { } | |
public abstract T Match<T>(Func<TLeft, T> onLeft, Func<TRight, T> onRight); | |
public sealed class Left(TLeft left) : Either<TLeft, TRight> | |
{ | |
private readonly TLeft _left = left; | |
public override T Match<T>(Func<TLeft, T> onLeft, Func<TRight, T> onRight) | |
=> onLeft(_left); | |
} | |
public sealed class Right(TRight right) : Either<TLeft, TRight> | |
{ | |
private readonly TRight _right = right; | |
public override T Match<T>(Func<TLeft, T> onLeft, Func<TRight, T> onRight) | |
=> onRight(_right); | |
} | |
} | |
public static class EitherExtensions | |
{ | |
public static Either<TLeft2, TRight2> SelectBoth<TLeft1, TLeft2, TRight1, TRight2>( | |
this Either<TLeft1, TRight1> source, | |
Func<TLeft1, TLeft2> selectLeft, | |
Func<TRight1, TRight2> selectRight) | |
{ | |
return source.Match<Either<TLeft2, TRight2>>( | |
onLeft: l => new Either<TLeft2, TRight2>.Left(selectLeft(l)), | |
onRight: r => new Either<TLeft2, TRight2>.Right(selectRight(r))); | |
} | |
public static Either<TLeft2, TRight> SelectLeft<TLeft1, TLeft2, TRight>( | |
this Either<TLeft1, TRight> source, | |
Func<TLeft1, TLeft2> selector) | |
{ | |
return source.SelectBoth(selector, r => r); | |
} | |
public static Either<TLeft, TRight2> SelectRight<TLeft, TRight1, TRight2>( | |
this Either<TLeft, TRight1> source, | |
Func<TRight1, TRight2> selector) | |
{ | |
return source.SelectBoth(l => l, selector); | |
} | |
public static Either<TLeft, TRight2> Select<TLeft, TRight1, TRight2>( | |
this Either<TLeft, TRight1> source, | |
Func<TRight1, TRight2> selector) | |
{ | |
return source.SelectRight(selector); | |
} | |
public static Either<TLeft, TRight> Join<TLeft, TRight>( | |
this Either<TLeft, Either<TLeft, TRight>> source) | |
{ | |
return source.Match( | |
onLeft: l => new Either<TLeft, TRight>.Left(l), | |
onRight: r => r); | |
} | |
public static Either<TLeft, TRight2> SelectMany<TLeft, TRight1, TRight2>( | |
this Either<TLeft, TRight1> source, | |
Func<TRight1, Either<TLeft, TRight2>> selector) | |
{ | |
return source.Select(selector).Join(); | |
} | |
public static Either<TLeft, TRight2> SelectMany<TLeft, TRight1, TRight2, U>( | |
this Either<TLeft, TRight1> source, | |
Func<TRight1, Either<TLeft, U>> k, | |
Func<TRight1, U, TRight2> s) | |
{ | |
return source.SelectMany(x => k(x).Select(y => s(x, y))); | |
} | |
public static Func<TRight1, Either<Exception, TRight2>> TryInvoke<TRight1, TRight2>(Func<TRight1, TRight2> func) | |
{ | |
return r1 => | |
{ | |
try | |
{ | |
var r2 = func(r1); | |
return new Either<Exception, TRight2>.Right(r2); | |
} | |
catch (Exception ex) | |
{ | |
return new Either<Exception, TRight2>.Left(ex); | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment