I have had some fun already using C# 7.1 new customizable async/await pipeline to do interesting things:

In this post I’ll be describing how I took on those past experiences and generalized the core idea of the second entry to provide a somewhat equivalent construct to F# computation expressions in C# by (ab)using the async/await machinery.

If you have never heard of F# computations expressions before I recommend reading the F# for fun and profit serie on it. Otherwise the TL;DR is that, without the monadic baggage, computation expressions essentially allow you to customize how a series of “statement” are chained together and plug your own logic in-between.

A famous example of such computation expression is the async workflow that lead to the creation of C# async/await in the first place (C# async/await being a more specialized/optimized form of its computation expression parent).

In a way what I’m describing in this post is basically how we can manage to return to the original definition of F# computation expressions using the more specialized C# async/await interface that was derived from it.

If you want to jump straight to the code grab it on GitHub. The repository also contains a Workbook file with a few examples utilizing the API so that you can get a feel of what it allows you to do.

For the others, here is the example given in the repository README of what the API looks like:

Option<int> TryDivide (int up, int down)
    => down == 0 ? None<int>.Value : Some.Of (up / down);

class MaybeBuilder : IMonadExpressionBuilder
{
    IMonad<T> IMonadExpressionBuilder.Bind<U, T> (IMonad<U> m, Func<U, IMonad<T>> f)
    {
        switch ((Option<U>)m) {
          case Some<U> some: return f (some.Item);
          case None<U> none:
          default: return None<T>.Value;
        }
    }

    public IMonad<T> Return<T> (T v) => Some.Of (v);
    public IMonad<T> Zero<T> () => None<T>.Value;
    // We don't have optional interface methods in C# quite yet
    public IMonad<T> Combine<T> (IMonad<T> m, IMonad<T> n) => throw new NotSupportedException ();
}

// Returns `Some 15`
ComputationExpression.Run<int, Option<int>> (new OptionExpressionBuilder (), async () => {
    var val1 = await TryDivide (120, 2);
    var val2 = await TryDivide (val1, 2);
    var val3 = await TryDivide (val2, 2);
    return val3;
})

// Returns `None`
ComputationExpression.Run<int, Option<int>> (new OptionExpressionBuilder (), async () => {
    var val1 = await TryDivide (120, 2);
    var val2 = await TryDivide (val1, 0);
    var val3 = await TryDivide (val2, 2);
    return val3;
})

In this snippet I’m lifting the example of the Maybe/Option monad I had talked about previously and encoding it via the computation expression mechanism to arrive at the same result. In that case the await keyword is used to represent the equivalent F# let! keyword which is a shortand for the Bind method of the computation expression builder (Bind is the core monadic operation that allows all those “statements” to be chained together).

To implement a custom computation expression you have to do two things:

  1. Define a type that implements the empty IMonad interface. This simply acts as a marker to be able to use the custom async machinery of the library.
  2. More interestingly, provide an implementation of the IMonadExpressionBuilder interface which is where you can put the code to customize the execution behavior of the computation expression block.

In the example above, the main customization is in the Bind method where we grab the result of executing the previous statement and then depending on the outcome either pass the result down the chain or short-circuit everything by returning None.

Another case where C# did a specialized implementation of what in F# is also encoded as a computation expression is IEnumerable<T> state machines via the yield operator.

With the library we can instead express this using the Combine method of the builder to achieve something like this (lifted from the examples workbook):

using static Neteril.ComputationExpression.ComputationExpression;
// The Enumerable monad is given in the library
using Neteril.ComputationExpression.Instances;

var result = ComputationExpression.Run<int, EnumerableMonad<int>> (new EnumerableExpressionBuilder (), async () => {
    var item = await (EnumerableMonad<int>)new [] { 1, 2, 3 };
    var item2 = await (EnumerableMonad<int>)new [] { 100, 200 };
    // We want back a enumeration containing the concatenation of (item, item2, item1 * item2)
    // for all successive values of item1 and item2
    await Yield (item);
    await Yield (item2);
    return item * item2;
});
string.Join (", ", result.Select (i => i.ToString ()));
// => [ 1, 100, 100, 1, 200, 200, 2, 100, 200, 2, 200, 400, 3, 100, 300, 3, 200, 600 ]

In this new sample, the monad is implemented in a way that the first await (again equivalent to F# let!) binds to each successive element of the given collections. The result is a EnumerableMonad<T> that is itself a IEnumerable<T> (i.e. you can further use LINQ on it).

Because in this case there is no way to hijack the C# yield operator directly (like F# would) this is instead represented here with the expression await Yield for intermediary values and return for the last one.

Here is the implementation of the builder for the sample to work (note the now implemented Combine method):

public class EnumerableExpressionBuilder : IMonadExpressionBuilder
{
    IMonad<T> IMonadExpressionBuilder.Bind<U, T> (IMonad<U> m, Func<U, IMonad<T>> f)
    {
       var previousEnumerableMonad = (EnumerableMonad<U>)m;
       return new EnumerableMonad<T> (previousEnumerableMonad.SelectMany (u => (EnumerableMonad<T>)f (u)));
    }

    IMonad<T> IMonadExpressionBuilder.Return<T> (T v) => new EnumerableMonad<T> (Enumerable.Repeat (v, 1));
    IMonad<T> IMonadExpressionBuilder.Zero<T> () => new EnumerableMonad<T> (Enumerable.Empty<T> ());

    IMonad<T> IMonadExpressionBuilder.Combine<T> (IMonad<T> m, IMonad<T> n)
    {
       var enumerableMonad1 = (EnumerableMonad<T>)m;
       var enumerableMonad2 = (EnumerableMonad<T>)n;
       // Simply use LINQ Concat method
       return new EnumerableMonad<T> (enumerableMonad1.Concat (enumerableMonad2));
    }
}

Interestingly of the early writings I remember reading about Monads and how it related to C#, the LINQ SelectMany operator was given as an example of a Bind operation in the monadic sense. With this we have the proof it’s indeed true.

The actual code enabling all of this is a fairly straightforward async method builder implementation. If you have read any of my previous articles I mentioned at the beginning of this post, it’s essentially doing the same thing with a bunch more hacks to cope with the added indirection of generic types in the IMonad<T> interface.

It also peeks a bit deeper into the internal structure of the async state machine generated by the compiler via the Machinist class so that it can control the actual stepping and awaiter values that are stored in it. This for instance allows the same state machine to be “rewinded” which is essential to support cases like the above Enumerable sample.