6
\$\begingroup\$

I want to write my C# code in a way which makes effects (state, I/O, etc) explicit in the type signature. I have started to understand monads, and have some acquaintance with algebraic effects, though not deeply so. I have attempted to implement a monad for working with algebraic effects in C#. The purpose is to allow you to provide an impure function which is an interpreter over an algebraic representation of your effects at the time that you run the monad, but not before. In theory this allows you to write all of your code in a pure way, and even pass a pure function in for the effects for the sake of unit testing etc. The monad is intended to be generic with respect to what types you use to represent your effects, and it is built with a struct in order to not be nullable.

public struct Eff<TOut, TIn, TReturn>
{
    private readonly Func<Func<TIn, TOut>,TReturn> _wrappedParameter;

    private Eff(Func<Func<TIn, TOut>, TReturn> wrappedParameter)
    {
        _wrappedParameter = wrappedParameter;
    }

    public static Eff<TOut,TIn,TReturn> Return(TReturn obj)
    {
        return new Eff<TOut,TIn,TReturn>(_ => obj);
    }

    public static Eff<TOut,TIn,TReturn> Do(TIn input, Func<TOut,TReturn> continuation)
    {
        return new Eff<TOut, TIn,TReturn>(f => continuation(f(input)));
    }

    public TReturn Run(Func<TIn, TOut> f)
    {
        return _wrappedParameter(f);
    }

    public Eff<TOut, TIn, TReturn2> Map<TReturn2>(Func<TReturn, TReturn2> g)
    {
        var wrappedParameter = _wrappedParameter;
        return new Eff<TOut, TIn, TReturn2>(f => g(wrappedParameter(f)));
    }

    public Eff<TOut,TIn,TReturn2> Bind<TReturn2>(Func<TReturn, Eff<TOut,TIn,TReturn2>> continuation)
    {
        var wrappedParameter = _wrappedParameter;
        return new Eff<TOut, TIn, TReturn2>(f => continuation(wrappedParameter(f)).Run(f));
    }
}

There are several things that could be brought up here (he concepts are so new with me that I have named things poorly; I haven't yet figured out how it would look to have multiple effect types playing together in your program; etc), but there's one thing in particular I am concerned about, which is that I am nervous that if an entire program were written inside this monad, it would have the potential to overflow the .NET stack. This is where I am most interested in getting a review. I believe that if you implement a computation expression in F# it ends up executing in a trampoline monad to keep the stack from overflowing. I am particularly concerned about this in a somewhat resource-constrained environment, say an old Windows CE device running Compact Framework 3.5 (which is actually a target platform for me).

\$\endgroup\$
  • 1
    \$\begingroup\$ Could you provide some usage examples? \$\endgroup\$ – t3chb0t Nov 9 '16 at 12:34
  • 2
    \$\begingroup\$ For posterity, there is now at least one nice looking C# library for Algebraic Effects here: github.com/nessos/Eff I have not tried it, but there are significant examples and test coverage, so I plan to try it soon. \$\endgroup\$ – solvingJ Dec 31 '17 at 19:57
2
\$\begingroup\$

This is not a direct and full answer, but whereas I did not end up getting the opportunity I was hoping for to try this out on a C# project (yet), I did hit a perfect case for one in an F# project. I rewrote this monad in F#, implemented some concrete operations with it, and used it. I have it running in production with no problems so far, but it isn't using the constrained environment that I was describing in the question, so it hasn't given me full confidence in that yet.

After having done that, there are two main criticisms I have of the version above:

  1. It's not very flexible. It really only allows you to have effects that conform to one type (Func<TIn,TOut>). Moreover it really only allows you to have one effect of this type.
  2. The three generic types are overkill and not necessary. The alternative will be demonstrated below.

What I came up with was this generic effect monad:

module Effect

type Eff<'E,'a> =
     private EffReq of ('E -> 'a)

let map (f : 'a -> 'b) (EffReq p : Eff<'E,'a>) : Eff<'E,'b> =
    EffReq (f << p)

// Argh F# blocks us from using unit, pure, or return...!
let init (x : 'a) : Eff<'E,'a> =
    EffReq (fun _ -> x)

let run (io : 'E) (EffReq p : Eff<'E,'a>) : 'a =
    p io

let join (e : Eff<'E,Eff<'E,'a>>) : Eff<'E,'a> =
    EffReq (fun performEff -> run performEff e |> run performEff)

let bind (f : 'a -> Eff<'E,'b>) (e : Eff<'E,'a>) : Eff<'E,'b> =
    join <| map f e

let request = EffReq

Specific operations can be implemented using request, e.g.:

module FileIO

open Effect

type FileIO = {
    ReadAllText : string -> string
}

type IOEff<'a> = Eff<FileIO,'a>

let readAllText (path : string) : IOEff<string> =
    Effect.request (fun io -> io.ReadAllText path)

I know this is in a different language than the original question but the conceptual aspect is the most important part.

I don't consider this implementation fully battle-hardened but it should allow me to evolve the C# version in a good direction.

Note that this is really a Reader monad: it reads from an impure environment (and because that environment is impure, technically it is able to write to it as well).

\$\endgroup\$
  • \$\begingroup\$ I'm thinking maybe you could have added this onto the original question instead. Do you have a usage example for C#? \$\endgroup\$ – Svek Feb 14 '17 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.