开发者

LINQ query expressions that operate on types (monads?) other than IEnumerable<T> -- Possible uses?

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.


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


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.


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


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".

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜