0% found this document useful (0 votes)
93 views

Tasks, Monads, and LINQ

The document discusses how Task<TResult> in C# satisfies the definition of a monad, with Task.FromResult acting as the Unit operator and an implementation of Then acting as the Bind operator. It also shows how common LINQ operators like SelectMany can be implemented for Task<TResult> by using the monadic Bind and Unit. Finally, it notes that Task<TResult> also satisfies the definition of a comonad, with the Result property acting as the Extract operator and ContinueWith acting as the Extend operator.

Uploaded by

panks
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
93 views

Tasks, Monads, and LINQ

The document discusses how Task<TResult> in C# satisfies the definition of a monad, with Task.FromResult acting as the Unit operator and an implementation of Then acting as the Bind operator. It also shows how common LINQ operators like SelectMany can be implemented for Task<TResult> by using the monadic Bind and Unit. Finally, it notes that Task<TResult> also satisfies the definition of a comonad, with the Result property acting as the Extract operator and ContinueWith acting as the Extend operator.

Uploaded by

panks
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 5

8/5/13 Tasks, Monads, and LINQ - .

NET Parallel Programming - Site Home - MSDN Blogs

Executive Visual Studio Application Languages .NET Framework Platform


Bloggers Lifecycle Development
Management

Parallel Programming with .NET


All about Async/Await, System.Threading.Tasks, System.Collections.Concurrent, System.Linq, and more…

Tasks, Monads, and LINQ


Stephen Toub - MSFT 3 Apr 2013 10:53 AM 10

A few years back, Wes Dyer wrote a great post on monads, and more recently, Eric Lippert wrote a terrific blog series
exploring monads and C#. In that series, Eric alluded to Task<TResult> several times, so I thought I’d share a few related
thoughts on Task<TResult> and the async/await keywords.

As both Wes and Eric highlight, a monad is a triple consisting of a type, a Unit function (often called Return), and a Bind
function. If the type in question is Task<T>, what are its Unit and Bind functions?

The Unit operator takes a T and “amplifies” it into an instance of the type:

public static M<T> Unit<T>(this T value);

That’s exactly what Task.FromResult does, producing a Task<T> from a T, so our Unit method can easily be implemented
as a call to FromResult:

public static Task<T> Unit<T>(this T value)


{
return Task.FromResult(value);
}

What about Bind? The Bind operator takes the instance of our type, extracts the value from it, runs a function over that
value to get a new amplified value, and returns that amplified value:

public static M<V> Bind<U, V>(


this M<U> m, Func<U, M<V>> k);

If you squint at this, and if you’ve read my previous blog post Implementing Then with Await, the structure of this
declaration should look eerily like the last overload of Then discussed:

public static Task<TNewResult> Then<TResult, TNewResult>(


this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation);

In fact, other than the symbols chosen, they’re identical, and we can implement Bind just as we implemented Then:

public static async Task<V> Bind<U, V>(


this Task<U> m, Func<U, Task<V>> k)
{
return await k(await m);
}

This is possible so concisely because await and async are so close in nature to the monadic operators. When you write an
async function that returns Task<V>, the compiler expects the body of the method to return a V, and it lifts (or amplifies)
that V into the Task<V> that’s returned from the method call; this is basically Unit (async, of course, also handles the
creation and completion of the returned Task for the case where an exception propagates out of the body of the async
method). Further, a core piece of the Bind operator is in extracting the value from the supplied instance, and that’s nicely
handled by await.

In Eric’s last post on monads, he talks about some of the C# LINQ operators, and how they can easily be implemented on
top of types that correctly implement a Unit and a Bind method:

static M<C> SelectMany<A, B, C>(


this M<A> monad,
Func<A, M<B>> function,
Func<A, B, C> projection)
{
return monad.Bind(
outer => function(outer).Bind(
inner => projection(outer, inner).Unit()));
}

Sure enough, with our Bind and Unit implementations around Task<T>, we can substitute in “Task” for “M”, and it “just
works”:

static Task<C> SelectMany<A, B, C>(


this Task<A> monad,
Func<A, Task<B>> function,
Func<A, B, C> projection)
{
blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx 1/5
8/5/13 Tasks, Monads, and LINQ - .NET Parallel Programming - Site Home - MSDN Blogs
return monad.Bind(
outer => function(outer).Bind(
inner => projection(outer, inner).Unit()));
}

What does it mean here to “just work”? It means we can start writing LINQ queries using the C# query comprehension
syntax with operators that rely on SelectMany, e.g.

int c = await (from first in Task.Run(() => 1)


from second in Task.Run(() => 2)
select first + second);
Console.WriteLine(c == 3); // will output True

Of course, we can implement SelectMany without Bind and Unit, just using async/await directly:

static async Task<C> SelectMany<A, B, C>(


this Task<A> task,
Func<A, Task<B>> function,
Func<A, B, C> projection)
{
A a = await task;
B b = await function(a);
return projection(a, b);
}

In fact, we can implement many of the LINQ query operators simply and efficiently using async/await. The C# specification
section 7.16.3 lists which operators we need to implement to support all of the C# query comprehension syntax, i.e. all of
the LINQ contextual keywords in C#, such as select and where. Some of these operators, like OrderBy, make little sense
when dealing with singular values as we have with Task<T>, but we can easily implement the others. This enables using
most of the C# LINQ query comprehension syntax with Tasks:

public static async Task<V> SelectMany<T, U, V>(


this Task<T> source, Func<T, Task<U>> selector, Func<T, U, V> resultSelector)
{
T t = await source;
U u = await selector(t);
return resultSelector(t, u);
}

public static async Task<U> Select<T, U>(


this Task<T> source, Func<T, U> selector)
{
T t = await source;
return selector(t);
}

public static async Task<T> Where<T>(


this Task<T> source, Func<T, bool> predicate)
{
T t = await source;
if (!predicate(t)) throw new OperationCanceledException();
return t;
}

public static async Task<V> Join<T, U, K, V>(


this Task<T> source, Task<U> inner,
Func<T, K> outerKeySelector, Func<U, K> innerKeySelector,
Func<T, U, V> resultSelector)
{
await Task.WhenAll(source, inner);
if (!EqualityComparer<K>.Default.Equals(
outerKeySelector(source.Result), innerKeySelector(inner.Result)))
throw new OperationCanceledException();
return resultSelector(source.Result, inner.Result);
}

public static async Task<V> GroupJoin<T, U, K, V>(


this Task<T> source, Task<U> inner,
Func<T, K> outerKeySelector, Func<U, K> innerKeySelector,
Func<T, Task<U>, V> resultSelector)
{
T t = await source;
return resultSelector(t,
inner.Where(u => EqualityComparer<K>.Default.Equals(
outerKeySelector(t), innerKeySelector(u))));
}

public static async Task<T> Cast<T>(this Task source)


{
await source;
return (T)((dynamic)source).Result;
}

Interestingly, Task<TResult> already has the members necessary to be considered “comonadic.” As Brian Beckman

blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx 2/5
8/5/13 Tasks, Monads, and LINQ - .NET Parallel Programming - Site Home - MSDN Blogs
discusses in his precis, a comonad is the dual of a monad, a triple consisting of the type and two operators: Extract (the
flip of Unit/Return) and Extend (the flip of Bind). Here I’ve taken a few liberties with the signatures from what Brian outlines,
such as swapping the order of some of the parameters:

public T Extract<T>(this W<T> self);


public W<U> Extend<T, U>(this W<T> self, Func<W<T>, U> func);

Task<TResult> already supports Extract, it’s just called Result:

public TResult Result;

And it already supports Extend, it’s just called ContinueWith:

public Task<TNewResult> ContinueWith<TNewResult>(


Func<Task<TResult>, TNewResult> func);

(In truth, to correctly implement all of the comonadic laws Brian outlines, we’d likely want to tweak both of these with a thin
layer of additional code to modify some corner cases around exceptions and cancellation, due to how Result propagates
exceptions wrapped in AggregateException and how ContinueWith tries to match thrown OperationCanceledExceptions
against the CancellationToken supplied to ContinueWith. But the basic idea stands.)

Most of the posts I write on this blog are about practical things. So, is any of this really useful in everyday coding with
Tasks and async/await? Would you actually want to implement and use the LINQ surface area directly for Tasks? Eh,
probably not. But it’s a fun to see how all of these things relate.

(Update 4/3/2013: Thanks to Aaron Lahman for pointing out the bug I had in my GroupJoin implementation. Fixed.)

Tweet 47 Like 21 Share 5 Save this on Delicious

Comments
4 Apr 2013 1:49 AM
Tom

Really like your posts, keep it up! And thanks to Eric I now know what a monad is.

I have aquestion concerning exceptions thrown in a Task returning method. Should all exceptions of a Task returning
method be thrown inside the returned task (so that the caller does not need to guide the method call itself inside a
try catch block) or is it ok to throw some exceptions (for example input validation or invalid state) before the
returned task is set up (fail fast) whereas the caller needs to be aware to protect the method call itself (besides
checking the returned task's result)?

(In the latter case the caller's code might get a little verbose especially when chaining tasks...)

Or is it just a matter of taste and you should document the method call accordingly? I'd really apprechiate your
thoughts or guideline on that.

Example:

public Task<int> CalcSthAsync(int x, int y)

// is it ok to throw exceptions here?

// for example concerning state (object already disposed etc.) or

// input validation:

if(x < 0)

throw new InvalidOperationException(...);

return Task.Factury.StartNew(() =>

// or should I throw all exceptions inside the task

// so that caller just needs to ckeck the returned task for exceptions

// and not guide the method call itself inside a try catch block?

if(x < 0)

throw new InvalidOperationException(...);


blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx 3/5
8/5/13 Tasks, Monads, and LINQ - .NET Parallel Programming - Site Home - MSDN Blogs
...

});

4 Apr 2013 6:21 AM


Tristan

@Stephen

Bit off topic, but still related to languages.

What about Roslyn ? Can you say something new ?

4 Apr 2013 6:22 AM


shoftee

Very awesome stuff. Should've figured it'd be this easy with await.

By the way, you missed 'async' in the method declaration:

static Task<C> SelectMany<A, B, C>(

this Task<A> task,

Func<A, Task<B>> function,

Func<A, B, C> projection)

A a = await task;

B b = await function(a);

return projection(a, b);

4 Apr 2013 8:04 AM


Max Battcher

« Would you actually want to implement and use the LINQ surface area directly for Tasks? Eh, probably not.»

Actually, this is essentially the point of Reactive Extensions (Rx) (and libraries conceptually near to Rx like
LinqToAwait): using monads and LINQ operators and LINQ-like operators you can do some very complex
asynchronous programming in very simple-appearing "query" structures.

4 Apr 2013 9:21 AM


Stephen Toub - MSFT

@Tom: Please see the document at www.microsoft.com/.../details.aspx, and specifically the end of page 3 /
beginning of page 4 where it talks about this.

@Tristan: The "Roslyn" forum at social.msdn.microsoft.com/.../roslyn is a good place to ask questions about
"Roslyn".

@shoftee: Thanks for the catch. Fixed.

@Max Battcher: Rx is about working with collections, and these kinds of query operations are very useful for
working with collections; they're less useful for types with at most one value, like Task<T>. Do you have an example
in mind where the query operators I've implemented in this post make using C# query comprehension syntax much
better for working with Task<T> than does async/await? I've not thought of a good one.

4 Apr 2013 12:02 PM


Richard

Presumably all of the "await" subjects should have ".ConfigureAwait(false)" added, since none of the continuations
rely on the synchronization context?

4 Apr 2013 12:08 PM


Stephen Toub - MSFT

@Richard: It's a good question. I debated doing so. Normally my answer would be a definitive "yes!" but because
most of these methods take user-supplied delegates that are invoked after an await, using ConfigureAwait(false) on
all of the awaits would mean that many of these delegates would not be run back on the original context, and it's
not clear whether that would be desirable or not. I'll leave it up to someone using this code to decide whether to
augment it appropriately, if anyone actually uses the code (as I mention in the post, this was more thought
experiment than anything else). In general, yes, try to use ConfigureAwait(false) on all awaits in libraries unless
blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx 4/5
8/5/13 Tasks, Monads, and LINQ - .NET Parallel Programming - Site Home - MSDN Blogs
there's a good reason not to.

5 Apr 2013 1:53 AM


Tom

Thanks for your response, Stephen, I was not aware of that document, it helped a lot. (Before commenting on your
blog I searched the web and actually assumed to find my question answered at stackoverflow - but I did not find
some answer.)

So I will put the "usage" checks (like argument and state validation) before the task creating block of the method.
Thanks again.

5 Apr 2013 8:48 AM


Stephen Toub - MSFT

@Tom: Great, glad you found the doc helpful.

22 Jul 2013 10:01 AM


Kurt S

Thanks so much for this post Stephen. I have been trying to get my head around how Tasks are related to
continuations, coroutines, monads and this is extremely helpful.

-Kurt

blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx 5/5

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy