I just read an old blog post by Wes Dyer showing how Linq Syntax uses Monads. I’ve been really struggling trying to understand Monads and learning Haskell over the last few months, and I’m really not there yet, but Wes’ post really lit a lightbulb. I’d naively thought Linq was simply all about IEnumerable<T> or IQueryable<T>, and for 99% of the examples and code out there, it is. But actually it’s far more generic than that.
Here’s a really silly example bastardised from Wes’ post. I’m going to wrap two integers in a context, or container, and then do some operation on them using Linq syntax so that I don’t have to explicitly dip into the container.
Let’s define our container first:
public class Container<T> { public T Item { get; private set; } public string Message { get; private set; } public Container(T item, string message) { Item = item; Message = message; } }
It just wraps an instance of some arbitrary type along with a message.
Next we need some extension methods, namely two varieties of SelectMany (which is effectively ‘bind’ from Haskell’s Monad), I’ve also added a ToContainer extension method, but it’s only to make things look nice and isn’t necessary for the Linq syntax to work:
public static class ContainerExtensions { public static Container<T> ToContainer<T>(this T item, string message) { return new Container<T>(item, message); } public static Container<U> SelectMany<T, U>(this Container<T> container, Func<T, Container<U>> selector) { return selector(container.Item); } public static Container<V> SelectMany<T, U, V>( this Container<T> container, Func<T, Container<U>> selector, Func<T, U, V> operation) { var container2 = container.SelectMany(selector); return container.SelectMany(x => selector(x).SelectMany(y => operation(x, y).ToContainer(container.Message + " " + container2.Message))); } }
Now we can use Linq Syntax to wrap two items (in this case integers) in containers, but still do operations on them without having to explicitly extract them from their containers. At the same time the containers themselves are combined as defined in the second SelectMany above:
public void LinqSyntaxWorksWithContainer() { var result = from a in 3.ToContainer("Hello") from b in 4.ToContainer("World") select a + b; Console.Out.WriteLine("result.Item = {0}", result.Item); Console.Out.WriteLine("result.Message = {0}", result.Message); }
Outputs:
result = 7 result.Message = Hello World
Is that not just amazing? Think of all the times you need pass around something wrapped in some context and then keep digging it out to do some operation on it, this technique could really clean up that code.
I’d very much recommend reading Wes’ blog post in full, and have a look at Monads, they are mind bending, but have the potential to radically simplify many programming challenges.
For an in depth example, check out this post by LukeH where he uses Linq Syntax to build a monadic parser. Chris Patterson has an excellent working example of such a parser in his Magnum library.
No comments:
Post a Comment