13
votes

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.

4
Btw., should this question be community wiki?stakx - no longer contributing
Just found another answer on monads (by Eric Lippert) here on SO that I find highly useful: stackoverflow.com/questions/2704652#2704795stakx - no longer contributing
By the way: You have the wrong type for "Bind" here: that's the signature of fmap in Haskell, or just map on lists, and is indeed what Select() does. The monadic bind, which is much more interesting, would be Func<M<A>, Func<A,M<B>>, M<B>>. This is concatMap on lists or, equivalently, SelectMany() on IEnumerable. The identity monad you've written still won't do anything interesting, but in a more interesting monad all the fun stuff would happen in SelectMany. Try the Maybe monad instead--that's "either a wrapped value or a null object".C. A. McCann
@camccann: Thanks for pointing out the correct type signatures. It actually occurred to me not long after posting that I got Bind wrong. -- I did implement the Maybe monad btw., and have marveled at how you get things like conditional evaluation/execution in a LINQ query expression (depending on whether the Maybe 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 the from..in..where..let..select LINQ syntax?stakx - no longer contributing

4 Answers

4
votes

Thoughts:

  • PushLINQ (me and Jon) - reverses LINQ to a push model (rather than the IEnumerable<T> pull model)
  • Reactive Framework / Reactive Extensions - another very different eventing model incorporating LINQ syntax
  • I wrote a threading API (ab)using the LINQ query syntax; I wasn't 100% convinced by it, so ditched it - but it was interesting; used from (SelectMany) etc to pick out branch/merge points completely unrelated to enumerables
4
votes

Even though I don't like doing this (as it feels a little like cheating), I think I have to answer my own question this time.

I have thought some more on this. My question was somewhat naïve. What it comes down to is that LINQ query expressions (e.g. frominwhereselect…) as well as foreach are syntactic sugar on top of other, more basic syntax.

  • foreach works on anything that implements a IEnumerator<T> GetEnumerator() method. IEnumerable<T> just happens to fulfill that condition.

  • Similarly, LINQ query expressions are translated according to some well-defined rules, e.g. from x in xs where x > 0 select -x becomes xs.Where(x => x > 0).Select(x => -x). As long as some type implements some or all of the query operator methods, the type can be used with LINQ for almost any purpose.

What remains of my question is what LINQ could be actually used for, apart from processing collections; and I think the answer would be "for very little else", because the structure of LINQ query expressions is quite rigid. You always need the fromin part. It seems like select… is always needed, too. If the "language" of the resulting expressions don't fit a particular potential scenario, any of the other keywords (let, orderby, groupby, etc.) won't improve the situation. LINQ was quite clearly designed with one goal in mind, that being the querying of data, and the resulting grammar actually restricts LINQ more than probably necessary.

I've compared the possibilities of LINQ for purposes other than querying data against the possibilities of F# computation expressions, and they seem more flexible because there aren't so many required keywords. Which tends to make them suitable for more scenarios.

3
votes

LinqToTwitter uses LINQ in an unusual way. Stuff in the 'from' clause is not, logically, an enumerable type. Have a look at the source :)

1
votes

It's useful for the same reasons monads are useful in Haskell. It's very much so possible to implement an Either<TLeft, TRight> type and supply it with query comprehension implementations for example. This can be used to write more functional code where you compose actions in a use-case with error-handling and logging built in and so on. Basically, take a look at languages like Haskell (or F# for something closer to home) and get a feel for how monads are used in real-life code.

Now, the issue is of course that this might not be very idiomatic C# code, even if it works and is maintainable, etc. It's important to not fight the language and not to write code that only you can understand. At the very least, your team should be "in on it".