Skip to content

Instantly share code, notes, and snippets.

@dbramucci
Created February 3, 2020 02:34
Show Gist options
  • Save dbramucci/d393a22612c1981c5f8069d640e17205 to your computer and use it in GitHub Desktop.
Save dbramucci/d393a22612c1981c5f8069d640e17205 to your computer and use it in GitHub Desktop.
C# Result Proof of Concept
using System;
using System.Threading;
// S is the type stored for successful computations, E for errors
public struct Result<S, E>
{
private bool isSuccessful;
private S successValue;
private E errorValue;
// User should only use Result<S, E>.Error and Result<S, E>.Success
// to construct Result objects
private Result(bool isSuccessful, S successValue, E errorValue)
{
this.isSuccessful = isSuccessful;
this.successValue = successValue;
this.errorValue = errorValue;
}
// Construct a Result representing an Error
public static Result<S, E> Error(E errorValue)
{
return new Result<S, E>(false, default (S), errorValue);
}
// Construct a Result representing a Success
public static Result<S, E> Success(S successValue)
{
return new Result<S, E>(true, successValue, default (E));
}
// Construct a value by telling the Result object how to make your
// Value when there was a success and how to make the value
// If an error happened
public R Match<R>(Func<S, R> whenSuccess, Func<E, R> whenError)
{
if (isSuccessful)
{
return whenSuccess(successValue);
}
else
{
return whenError(errorValue);
}
}
// Extract the successful value, using a provided value in case the
// Result is an error.
public S FromDefault(S default_)
{
return Match(s => s, e => default_);
}
// Perform an action for the Success and Error cases
// Without returning a value, this is useful when you
// Don't want to get a result from the Match.
public void Do(Action<S> whenSuccess, Action<E> whenError)
{
if (isSuccessful)
{
whenSuccess(successValue);
}
else
{
whenError(errorValue);
}
}
// Apply a function only if the Result is a success
public Result<T, E> MapSuccess<T>(Func<S, T> f)
{
return Match(s => Result<T, E>.Success(f(s)), e => Result<T, E>.Error(e));
}
// Apply a function only if the Result is an error
public Result<S, F> MapError<F>(Func<E, F> f)
{
return Match(s => Result<S, F>.Success(s), e => Result<S, F>.Error(f(e)));
}
// Apply functions depending on what the Result is, storing creating a new Result
public Result<T, F> BiMap<T, F>(Func<S, T> whenSuccess, Func<E, F> whenError)
{
return Match(s => Result<T, F>.Success(whenSuccess(s)),
e => Result<T, F>.Error(whenError(e)));
}
// If the first Result is an error, use its error
// Otherwise return the second results success/error
public Result<T, E> And<T>(Result<T, E> result)
{
return AndThen(_ => result);
}
// If the first Result is an Error return that,
// Otherwise call the provided function on the successful value
// and return the result of that.
public Result<T, E> AndThen<T>(Func<S, Result<T, E>> f)
{
return Match(s => f(s),
e => Result<T, E>.Error(e));
}
}
class MainClass {
public static Result<int, string> SafeDivide(int n, int d)
{
if (d == 0)
{
return Result<int, string>.Error($"Zero Division: n/d = {n}/{d}");
}
else
{
return Result<int, string>.Success(n/d);
}
}
public static void Main (string[] args) {
Console.WriteLine (
Result<String, String>
.Error("Hello World")
.Match(s => {
var temp = "win: ";
temp += s;
return temp;
},
e => $"fail: {e}")
);
Result<String, String>
.Success("hello world there lower")
.BiMap(Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase,
Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase)
.Do(s => Console.WriteLine("win: " + s),
e => {});
Console.WriteLine(
SafeDivide(5618, 25)
.AndThen(s => SafeDivide(s, 4))
.MapSuccess(s => s * 2345)
.AndThen(s => SafeDivide(s, 0))
.MapSuccess(s => s * 4327)
.MapSuccess(s => s + 42)
.Match(s => $"Success: {s}",
e => $"Error: {e}")
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment