Monads and monad transformers for mere mortals in pure C#. RUS
25 June 2014 – Karelia
After publication of this post, some readers complain that example are too artificial and don’t show any advantages of monads. So if you want more interesting, but also more complicated example then you could read this post and after that return here
During migration of a small project to fsharp, I thought a lot about using monads in practice. Subject fascinated me enough and I decided to describe my experience in a couple of posts. Unfortunately all the material demanded decent knowledge of monads and monad transformers, at the same time I wanted to make a material without any links to external materials, without a lot of words and simple for ordinary programmers who are not burdened by the knowledge of mathematics and syntax of some functional programming language. Thus was born the idea to write another explanation of what is a monad. Since this post is aimed at programmers, and it will be approached by code and not by the text or mathematics. Ready? Let’s go.
Lets look at some simple code:
GetData function imitates a query to a data source and randomly returns instance of required data type or null. Compare function uses GetData for retrieving two values, compares them and writes output to console. What we as a good programmers should fix at first in this sample? Right, we should add null checks and use style of defensive programming.
Ok, code now looks more safe, but we have repeated code, lets remove it to support DRY principle.
Looks better, but anyway we must add if check for every GetData invocation. Lets move if check into Defend function.
Just perfect, but we have a problem in case of using some other type, for example class Test. During execution it will be downcasted to System.Object and we will not be able to use its members.
We can avoid this problem with generic parameters.
New problem, we are trying to return a type string instead of a type Test. And what to do? Lets create some type which could store a value or an error string.
Looks beautiful. In action:
Problem again, our code returns Check< Check< T > > instead of Check< T > and it is not very good. We will change our code to avoid this problem.
Now null check test lives in a Lift function. And the main task of the function is to wrap any function which returns T into function which returns Check< T >. Problem solved. We can think about it in this way: we have some functions and our function Defend. But to use them together we need to adapt all used functions to Defent function. And this is main purpose of the lift function.
Problem, problem, problem. At the end we return result not wrapped into the Check type. Lets write some helper function which wraps any type T into the Check. The name of function will be return. Lets add it and do some refactoring
Awesome, it works as expected. So what do we have? Wrapper type Check over any type T, two functions Defend and Return. And this is all what we need to write some defensive code without null checks. But we can write some other wrapper type and define functions Return and Defend over it with a different functionality in function Defend. It will allow us to use the same code, but now with different effect. For example instead of checking we can implement async effect(and we do that later). This pattern is well known as monad, and function Defnd has name Bind by convention in monad pattern. One minor problem is that our consuming code looks not very beautiful in terms of wrapped functions and we as imperative developers prefer simple line by line code. Fortunately for us, some solutions are already here. In some programming languages we have support for syntactic sugar over monads: linq expressions in c#, do notation in Haskell and computation expressions in fsharp. Computation expressions is not only for monad syntax, but we will discuss it in next posts. Lets try to adapt our code to linq expressions, we should implement extension function SelectMany for our wrapper type.
Now everything is ok. We can use this pattern to add syntactic sugar for other wrapper types. We can use the same code for differnet monads, until monad carries within itself the same type. For example, compare the code for the Async monad
and the Check monad
Very cool, but there is a problem with the composition of monads. We would use the same code with functions that return Async< Check < T > >. However, our code in the Bind function of the type Async knows nothing about nested type Check, so our code will not work, our bind function unwraps only Async and returns Check < T > instead of T. Here monads transformers come into play. What is a monad transformer? This is a sort of thing which is taking an unknown monad as input, adds some functionality of other monad and returns a combined monad. Suppose in our case with monads Async< T > and Check< T > which could not be used together, we can write monads transformers AsyncT< T,ParentMonad > and CheckT< T, ParentMonad >.For our case Async< Check< T > > we can safely do something like this:
Usually all “monad in c#”” tutorials ends here with words: this kind of things could exists in languages like Haskell which supports higher kinded types but not in c#. But we as a smart developers well know that we could implement some workaround over any problem, so lets try to create one. We will look on a typical example with Functor interface. When we solve that problem we will be able to use the same workaround for implementation of monad transformers in c#. So Functor interface looks like this:
Nothing special is here, it describes a function which takes “a” value wrapped into a type T, unwraps it, applies function f to unwrapped value and finally wraps result into the type T. Everything seems to be ok, but we can’t write this code in C#. C# doesn’t support usage of type variable T as type constructor. I don’t want to describe whole problem here and better way to understand this restriction is to copy interface definition into IDE and play with it. It is a good puzzle. Lets try to analyse that problem and solve it step by step. Why do we need type T here? We need it as a constraint to input and output of FMap function. They should have the same wrapper type over different wrapped types. It guards us from incorrect implementations which takes Check< AType > and returns List< BType >. So we need to mark generic type by some other non generic type. How can we do that. It is simple.
Interesting. First of all we can be sure that instance of type Wrapper is the instance of type WrapperImpl. But we need to keep wrapped type somewhere to do safe upcast. Lets introduce special type container, which stores generic type marker with wrapped type. Also we need to rewrite WrapperImpl to support it.
Now we can add helper method for safe upcasts.
And now we can solve our Functor interface problem with type T.
Bingo. One restriction is to follow single inheritance pattern when describing our container classes. Lets try to use out Functor interface in csharp’s idiomatic way.
Now we have everything to implement IMonad interface and later build monad transformers on top of it.
It should be clear what is going on here. We took our workaround for functor interface and applied it to our IManad interface. Now we can rewrite our Check monad and adapt it to out IMonad interface. Also now we can implement Async monad. Async monad implementation can be used as an example of how to adapt some existing type to monadic interface. In our case we will build Async monad on top of Task< T > type.
Ok Check monad works but what about Async< T >?
It works as expected, we have polymorphic monads. Time for transformers. We have type Async< Check< T > > it is an Async monad over type Check< T >, but we want to convert it into a monad Async<Check<_>> over the type T. How to do that? We need to wrap Async<Check> into a monad over type T. Lets name it as CheckT transformer for the Check monad. At the end we will have a type like this CheckT< Async< Check< T >>>, it is very similar to a sliced bread. Main thing is that CheckT implements interface IMonad over T and not over SomeMonad< Check< T > > >. Repeat one more time: CheckT is a wrapper for types like SomeOtherMonad< CheckMonad< T > > and Lift function for CheckT convert functions which returns SomeMonad< CheckMonad< T > > into functions which returns CheckT< SomeOtherMonad< CheckMonad< T > > >. In Return function it will wrap value into the Check type, and after that will use Return function of other monad to wrap it one more time and finally cast result to type CheckT. Bind function is a little bit harder to understand, but logic is the same. So lets implement the CheckT type. For better understanding I separated Check types into: a container CheckedVal< T >, a monad adapter CheckM for the type CheckedVal< T > and a monad transformer CheckT for the type CheckedVal< T >. This separation is artificial and you can merge CheckedVal< T > and CheckM into the single one. Most attention should be paid to place where we put internal monad marker in type CheckT. It is defined in parent type CheckForT< TMI >.CheckT< T >, but not in generic type CheckForT.CheckT< T,TMI >. This constraints our IMonad functions to use the same internal monad marker everywhere. And it is similar to partial type construction. So magic lives here:
And now we are ready to implement code for Async< CheckedValue > monad.
Full code here. So we composed two monads into single one and this is a real benefit for us, now we can write generic code which is polymorphic for different monad types and have a way to cmpose monads. One of the monads was just a wrapper over existing type Task. It is clear that we have problems with result unwrapping, but it can be avoided by moving final code into the monad syntax or by creating helper methods like runAsync. I hope this post was helpful for you and now you will be able to read articles about interesting solutions for problems like parsing and so on, described in therms of monads. In the next chapter we will look at the differences between computation expressions and monads, will find that monads are Turing complete and will see how to use monads to solve real world problems and to create DSLs.