I'm reading the book Real-world functional programming by Tomas Petricek and Jon Skeet and I'm having a hard time digesting the section on computation expressions1) (aka monads).
Through this book, I learnt that — contrary to my previous experiences — LINQ query expressions aren't restricted to IEnumerable<T>
, but can work on other custom types as well. This seems very interesting to me, and I am wondering if there are scenarios where the query expression syntax (from x in ... select ...
) would be a nice fit.
Some background info:
Apparently, such custom types are called computation types, which are portrayed as being essentially the same thing as monads in Haskell. I have never been able to grasp what exactly monads are, but according to the book, they are defined through two operations called bind and return.
In functional programming, the type signatures of these two operations would be (I think):
// Bind : M<A'> -> (A' -> B') -> M<B'>
//
// Return : A' -> M<A'>
where M
is the monadic type's name.
In C#, this corresponds to:
Func< M<A>, Func<A,B>, M<B> > Bind;
Func< A, M<A> > Return;
It turns out that LINQ's Enumerable.Select
(the projection operator) has exactly the same signature as the bind operation with M := IEnumerable
.
My custom LINQ computation type:
Using this knowledge, I can now write a custom computation type which is not IEnumerable
:
// my custom computation type:
class Wrapped<A>
{
// this corresponds to the Return operation:
public Wrapped(A value)
{
this.Value = value;
}
public readonly A Value;
}
static class Wrapped
{
// this corresponds to the Bind operation:
public static Wrapped<B> Select<A, B>(this Wrapped<A> x, Func<A,B> selector)
{
return new Wrapped<B>(selector(x.Value));
}
}
And now I can use Wrapped<T>
in LINQ query expressions, e.g.:
Wrapped<int> wrapped = new Wrapped<int>(41);
Wrapped<int> answer = from x in wrapped // works on int values instead
select x + 1; // of Wrapped<int> values!
Of course this example is not very useful, but it demonstrates how query expressions can be made to do something else than working with collections, e.g. wrapping and unwrapping values with some type.
Question:
The above computation type doesn't seem very useful. Therefore I wonder, what other reasonable uses (besides processing collections) could there be that make use of LINQ query expressions?
1) Section 12.4: "Introducing alternative workflows", starting on page 334.