Skip to content

Instantly share code, notes, and snippets.

@AlexeyRaga
Created March 7, 2024 14:31
Show Gist options
  • Save AlexeyRaga/9180b7d9f18c3ecd01fc401f1f648d88 to your computer and use it in GitHub Desktop.
Save AlexeyRaga/9180b7d9f18c3ecd01fc401f1f648d88 to your computer and use it in GitHub Desktop.
Either in C#
public abstract record Either<TLeft, TRight>
{
// there can be no more cases defined!
private Either() {}
public sealed record Left(TLeft Value) : Either<TLeft, TRight>;
public sealed record Right(TRight Value) : Either<TLeft, TRight>;
}
public static class Either
{
public static Either<TLeft, TRight> Left<TLeft, TRight>(TLeft value) => new Either<TLeft, TRight>.Left(value);
public static Either<TLeft, TRight> Right<TLeft, TRight>(TRight value) => new Either<TLeft, TRight>.Right(value);
}
public static class EitherExtensions
{
public static bool IsLeft<TLeft, TRight>(this Either<TLeft, TRight> either) => either is Either<TLeft, TRight>.Left;
public static bool IsRight<TLeft, TRight>(this Either<TLeft, TRight> either) => either is Either<TLeft, TRight>.Right;
public static Either<TLeft, TResult> Select<TLeft, TRight, TResult>(this Either<TLeft, TRight> either, Func<TRight, TResult> selector) =>
either switch
{
Either<TLeft, TRight>.Left left => Either.Left<TLeft, TResult>(left.Value),
Either<TLeft, TRight>.Right right => Either.Right<TLeft, TResult>(selector(right.Value)),
_ => throw new InvalidOperationException("Invalid state")
};
public static Either<TLeft, TResult> SelectMany<TLeft, TRight, TResult>(this Either<TLeft, TRight> either, Func<TRight, Either<TLeft, TResult>> selector) =>
either switch
{
Either<TLeft, TRight>.Left left => Either.Left<TLeft, TResult>(left.Value),
Either<TLeft, TRight>.Right right => selector(right.Value),
_ => throw new InvalidOperationException("Invalid state")
};
public static Either<TLeft, TResult> SelectMany<TLeft, TRight, TCollection, TResult>(this Either<TLeft, TRight> either, Func<TRight, Either<TLeft, TCollection>> collectionSelector, Func<TRight, TCollection, TResult> resultSelector) =>
either switch
{
Either<TLeft, TRight>.Left left => Either.Left<TLeft, TResult>(left.Value),
Either<TLeft, TRight>.Right right => collectionSelector(right.Value).SelectMany(collection => Either.Right<TLeft, TResult>(resultSelector(right.Value, collection))),
_ => throw new InvalidOperationException("Invalid state")
};
public static Either<TResult, TRight> SelectLeft<TResult, TLeft, TRight>(this Either<TLeft, TRight> either, Func<TLeft, TResult> selector) =>
either switch
{
Either<TLeft, TRight>.Left left => Either.Left<TResult, TRight>(selector(left.Value)),
Either<TLeft, TRight>.Right right => Either.Right<TResult, TRight>(right.Value),
_ => throw new InvalidOperationException("Invalid state")
};
}
// setup some types
var name = Either.Right<string, string>("Alexey");
var height = Either.Right<string, int>(188);
var error = Either.Left<string, string>("Boom!");
// use LINQ expression
var linked = name.SelectMany(x => height.Select(y => (x, y)));
Console.WriteLine(linked); // Right { Value = (Alexey, 188) }
// use LINQ syntax
var result =
from n in error
from h in height
select (n, h);
Console.WriteLine(result); // Left { Value = Boom! }
// use pattern matching
var patternMatched = error switch
{
Either<string, string>.Left(var value) => $"It's Left: {value}",
Either<string, string>.Right(var value) => $"It's Right: {value}",
_ => "Unknown type" // It's a good practice to include a fallback case, though it's not strictly necessary here.
};
Console.WriteLine(patternMatched);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment