Skip to content

Instantly share code, notes, and snippets.

@eiriktsarpalis
Last active April 6, 2020 10:26
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save eiriktsarpalis/8d87ce971b432d80658d33768ff7b0fa to your computer and use it in GitHub Desktop.
Save eiriktsarpalis/8d87ce971b432d80658d33768ff7b0fa to your computer and use it in GitHub Desktop.
Encoding a Maybe monad in C# using Eff, https://github.com/nessos/Eff
namespace Maybe
{
public struct Maybe<T>
{
public bool HasValue { get; }
public T Value { get; }
private Maybe(T value)
{
HasValue = true;
Value = value;
}
public static Maybe<T> Nothing { get; } = new Maybe<T>();
public static Maybe<T> Just(T value) => new Maybe<T>(value);
}
}
namespace Maybe
{
using Nessos.Eff;
public class MaybeEffect<T> : Effect<T>
{
public Maybe<T> Result { get; set; }
}
public static class MaybeEffect
{
public static MaybeEffect<T> Nothing<T>() => new MaybeEffect<T> { Result = Maybe<T>.Nothing };
public static MaybeEffect<T> Just<T>(T t) => new MaybeEffect<T> { Result = Maybe<T>.Just(t) };
}
}
namespace Maybe
{
using Nessos.Eff;
using System;
using System.Threading.Tasks;
public static class MaybeEffectHandler
{
public static Maybe<TResult> Run<TResult>(Eff<TResult> eff)
{
switch (eff)
{
case ExceptionEff<TResult> setException:
throw setException.Exception;
case ResultEff<TResult> setResult:
return Maybe<TResult>.Just(setResult.Result);
case DelayEff<TResult> delay:
return Run(delay.Continuation.MoveNext());
case AwaitEff<TResult> awaitEff:
var handler = new MaybeEffectHandlerImpl<TResult>(awaitEff.Continuation);
awaitEff.Awaiter.Accept(handler);
return handler.Result;
default:
throw new NotSupportedException($"{eff.GetType().Name}");
}
}
public static Maybe<Unit> Run(Eff eff)
{
return Run(Helper());
async Eff<Unit> Helper() { await eff; return Unit.Value; }
}
private class MaybeEffectHandlerImpl<TResult> : EffectHandler
{
private readonly IEffStateMachine<TResult> _continuation;
public MaybeEffectHandlerImpl(IEffStateMachine<TResult> continuation)
{
_continuation = continuation;
}
public Maybe<TResult> Result { get; private set; } = Maybe<TResult>.Nothing;
public async override Task Handle<TValue>(EffectAwaiter<TValue> awaiter)
{
switch (awaiter.Effect)
{
case MaybeEffect<TValue> me:
if (me.Result.HasValue)
{
awaiter.SetResult(me.Result.Value);
var next = _continuation.MoveNext();
Result = MaybeEffectHandler.Run(next);
}
break;
}
}
public async override Task Handle<TValue>(EffAwaiter<TValue> awaiter)
{
var result = MaybeEffectHandler.Run(awaiter.Eff);
if (result.HasValue)
{
awaiter.SetResult(result.Value);
var next = _continuation.MoveNext();
Result = MaybeEffectHandler.Run(next);
}
}
}
}
}
namespace Maybe
{
using System;
using System.Threading.Tasks;
class Program
{
static async Eff<int> Divide(int m, int n)
{
return (n == 0) ? await MaybeEffect.Nothing<int>() : await MaybeEffect.Just<int>(m / n);
}
static async Eff DivideAndReportToConsole(int m, int n)
{
Console.Write($"Calculating {m} / {n}: ");
var result = await Divide(m, n);
Console.WriteLine($"Got {result}!");
}
static async Eff Test()
{
for (int i = 0; i < 10; i++)
{
await DivideAndReportToConsole(23, 5 - i);
}
}
static void Main()
{
MaybeEffectHandler.Run(Test());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment