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.
fmap
in Haskell, or justmap
on lists, and is indeed whatSelect()
does. The monadic bind, which is much more interesting, would beFunc<M<A>, Func<A,M<B>>, M<B>>
. This isconcatMap
on lists or, equivalently,SelectMany()
onIEnumerable
. The identity monad you've written still won't do anything interesting, but in a more interesting monad all the fun stuff would happen inSelectMany
. Try the Maybe monad instead--that's "either a wrapped value or a null object". – C. A. McCannBind
wrong. -- I did implement theMaybe
monad btw., and have marveled at how you get things like conditional evaluation/execution in a LINQ query expression (depending on whether theMaybe
value contains a value or not). But that was about the only other interesting monad that fits more or less into LINQ and does something sensible. Do you know of more monads that fit together nicely with thefrom..in..where..let..select
LINQ syntax? – stakx - no longer contributing