Part II of the series Fun with Functional C#.
March 16, 2016

In this installment of Fun with Functional C#, we’re going to be implementing the reader monad, and attempting to figure out if it’s actually useful. The reader monad was the first monad I implemented when I found out that SelectMany was bind. I thought it would be a good way to get rid of runtime dependency injection errors, but it turned out not to be the panacea I’d hoped for.

Implementation

The first thing I did was implement SelectMany, but it had some quirks that I’ll discuss in the next installment of this series. For now, let’s just look at the implementation:

public class Reader<E, A>
{
private Func<E, A> f;
{
f = func;
}

{
return f(env);
}
}

This is basically equivalent to

newtype Reader e a = Reader { runReader :: (e -> a) }

Now we define the bind operation with an extension method:

public static class ReaderExt
{
public static Reader<E, C> SelectMany<E, A, B, C>(
Func<A, B, C> projection
)
{
return new Reader<E, C> (env => {
});
}
}

and we get query syntax!

class Main
{
public static void Main()
{
var showConfig =
from uri in GetUri()
from timeout in Ask(config => config.TimeoutSeconds)
select \$"Absolute uri: '{uri.AbsoluteUri}', Timeout: {timeout} seconds.";
}

// If we make this function polymorphic over the environment type,
// C# won't be able to automatically infer the type.
{
}

{
return new Reader<Config, Uri> (config => {
return new Uri(config.EndpointUrl);
});
}
}

public class Config
{
public Config(string endpointUrl, int timeoutSeconds, Pinger pinger)
{
EndpointUrl = endpointUrl;
TimeoutSeconds = timeoutSeconds;
Pinger = pinger;
}

public string EndpointUrl {get;}
public int TimeoutSeconds {get;}
public Pinger Pinger {get;}
}

But is it useful?

In an imperative language like C#, the reader monad seems rather out of place. A big sell of the reader monad is that you can encode, at a type level, the fact that a function accesses a certain type of data. But in C#, you could write something like:

public class ConfigReader
{
{
_config = config;
_pinger = pinger;
}

{
return new Reader<Config, Response>(config => {
return config.Pinger.Ping(config.EndpointUrl);
});
}

public Response PingEndpointImplicit()
{
return _pinger.Ping(_config.EndpointUrl);
}
}

PingEndpointImplicit has absolutely no type-level indication that it requires a config to do its job; you actually need to look at the implementation to figure out that it would fail if you moved the function out of this class1.

PingEndpointReader can be moved out of the class and still work, but it could have just as easily been written:

public Reader<Config, Response> PingEndpointReaderImplicit()
{
return new Reader<Config, Response>(config => {
// Use the _pinger instance instead
return _pinger.Ping(config.EndpointUrl);
});
}

and you can’t move this function out of the class without breaking it. There’s not much point in going to all the trouble of encoding “invariants” into types if they can be broken at any time.

Importing functional constructions into imperative languages gives developers the tools to create self-contained, pure functions, but still relies on developer discipline to make sure it’s done correctly. And relying on developer discipline just doesn’t work, because unless you actually read the function implementation, there’s no way of knowing if someone decided to meet a deadline by grabbing some implicit state “just this once”. Even moving the function into a static class isn’t an ironclad guarantee of purity.

And if you need to read a function’s implementation to understand its properties, the promise of functional programming- composition and reusability and referential transparency- is broken.

So it’s useless?

No, I don’t think it’s useless. Programs exist along a spectrum of purity, and all things being equal, I’d rather live in a codebase on the purer end, even if every single function wasn’t pure.

But purity is only one side of the story. The reader monad is basically a convenient2 way to thread a read-only state through a bunch of functions. This means you don’t need to rely on the class object graph for dependencies, which can be useful because changing the object graph is frequently a real pain.

But the reader monad is only convenient in languages where there’s adequate type inference and syntax. If you’re using a language like C#, there are times where you’ll have to deal with the cumbersome type Reader<Env, A>, even with hacks like language-ext. So the reader monad turns out not to be all that convenient in C#, nor does it provide the type-level safety that it does in purer languages.

Interestingly, Scala has slightly better type inference than C#, but still doesn’t have the purity of Haskell. In Scala, the reader monad is seen as a viable alternative (or a supplement to) more traditional approaches to DI, like the cake pattern. In C#, it seems that we only need to push the convenience side a little bit3 for the reader monad to become useful, or encounter a situation where not using the reader monad is more inconvenient than using it. I’ve actually encountered one situation where that’s the case, but that will have to be the subject of another post.

In general, however, I think idiomatic approaches to DI in C# strike the best balance between ease of use, utility, and expressiveness.

1. Being able to move a function out of the class without anything breaking is a reasonable proxy for the purity of the function.

2. “Convenience” often means that we eliminate the need for a repetitive, error-prone activity (like threading state through functions), which means something that can look like a mere “convenience” can actually result in higher code quality.

3. Perhaps with better type inference, or some language-ext-style trickery.