Fun with pure C# and monads

05/20/2017

2 minutes to read

In this article

We are all fascinated by pure languages like Haskell, but unfortunately have to code in C#. We could complain to our neighbor all day, but instead, what if we could also do something really pure in C#. Actually we can, it’s called LINQ:

var sum = from a in new[] { 1 } from b in new[] { 2 } from c in new[] { 3 } where a + b > c select a + b + c;

Don’t know about you, looks pretty darn pure to me. No side effects given. However, we can’t do much without side effects in the real world. In Haskell, as a pure language, we have to encapsulate all side effects into monads.

What if we wanted to write pure in C#, but also needed side effects. C# supports side effects, but only in imperative style:

Console.WriteLine(sum);

Seems legit. But to us, functional people, not much fun. What if we could write pure, but still have side effects, whenever needed. Luckily, we can define a fancy monad to help us:

public delegate T IO<out T>(); public static IO<R> SelectMany<S, C, R>(this IO<S> A, Func<S, IO<C>> foo, Func<S, C, R> bar) { var a = A(); var B = foo(a); var b = B(); var c = bar(a, b); return () => c; } public static IO<T> Combine<T>(IO<T> a, Action b) { return new Func<IO<T>, IO<T>>(x => { b(); return x; })(a); } public static IO<T> Where<T>(this IO<T> t, Func<T, bool> foo) { return () => (foo(t()) ? t : Combine(t, () => { Console.WriteLine("We screwed up!"); }))(); } public static IO<R> Select<S, R>(this IO<S> s, Func<S, R> foo) { return () => new Func<IO<S>, R>(x => foo(x()))(s); }

Now let’s do some serious pure coding with side effects:

var A = new IO<int>(() => { Console.WriteLine("A"); return 6; }); var B = new IO<int>(() => { Console.WriteLine("B"); return 7; }); var C = new IO<int>(() => { Console.WriteLine("C"); return 10; }); var sum = from a in A from b in B from c in C let x = 10 where a > b select a + b + c + x;

Now that looks real fun to me all right.

Henceforth, you can be really sneaky, and hide side effects in places where your coworker least expects them: in pure LINQ expressions. Happy debugging!