Skip to content

Instantly share code, notes, and snippets.

@iSynaptic
Last active April 19, 2017 21:55
Show Gist options
  • Save iSynaptic/610b0879dd4d39bfb16142593f45150a to your computer and use it in GitHub Desktop.
Save iSynaptic/610b0879dd4d39bfb16142593f45150a to your computer and use it in GitHub Desktop.
Task<T> Monad Example
void Main()
{
Task<int> monadicTask = from x in Task.Factory.StartNew(() => 6)
from y in Task.Factory.StartNew(() => 7)
let computation = x * y
select computation;
Console.WriteLine(monadicTask.Result); // writes 42;
}
public static class TaskExtensions
{
private static bool HasTaskCompletedSuccessfully(this Task @this)
{
if(@this == null) throw new ArgumentNullException("this");
if(!@this.IsCompleted) throw new InvalidOperationException("You can only test completed tasks.");
return !@this.IsFaulted && !@this.IsCanceled;
}
private static void PropigateTaskOutcome<T>(Task<T> source, TaskCompletionSource<T> tcs)
{
if(source == null) throw new ArgumentNullException("source");
if(!source.IsCompleted) throw new InvalidOperationException("You can only propigate completed tasks.");
if(tcs == null) throw new ArgumentNullException("tcs");
if(!TryPropigateTaskWithoutResult(source, tcs))
tcs.SetResult (source.Result);
}
private static bool TryPropigateTaskWithoutResult<T, TTarget>(Task<T> source, TaskCompletionSource<TTarget> tcs)
{
if(source == null) throw new ArgumentNullException("source");
if(!source.IsCompleted) throw new InvalidOperationException("You can only propigate completed tasks.");
if(tcs == null) throw new ArgumentNullException("tcs");
if(source.IsFaulted)
{
tcs.SetException (source.Exception);
return true;
}
if(source.IsCanceled)
{
tcs.SetCanceled();
return true;
}
return false;
}
private static Task<T> ToTask<T>(this T value)
{
return Task.FromResult(value);
}
// C# compiler optimized select many (implemented in a non-optimal way [ie. using the "real" bind function])
public static Task<TResult> SelectMany<T, TIntermediate, TResult>(this Task<T> @this, Func<T, Task<TIntermediate>> selector, Func<T, TIntermediate, TResult> combiner)
{
return @this.SelectTask(t => selector(t).SelectTask(u => combiner(t, u).ToTask()));
}
//"real" monadic bind function
public static Task<TResult> SelectMany<T, TResult>(this Task<T> @this, Func<T, Task<TResult>> selector)
{
return SelectTask(@this, selector);
}
public static Task<TResult> SelectTask<T, TResult>(this Task<T> @this, Func<T, Task<TResult>> selector)
{
if(@this == null) throw new ArgumentNullException("this");
if(selector == null) throw new ArgumentNullException("selector");
var tcs = new TaskCompletionSource<TResult>();
@this.ContinueWith (t =>
{
if(!TryPropigateTaskWithoutResult(t, tcs))
selector(t.Result).ContinueWith (t2 => PropigateTaskOutcome(t2, tcs));
});
return tcs.Task;
}
public static Task<TResult> Select<T, TResult>(this Task<T> @this, Func<T, TResult> selector)
{
if(@this == null) throw new ArgumentNullException("this");
if(selector == null) throw new ArgumentNullException("selector");
var tcs = new TaskCompletionSource<TResult>();
@this.ContinueWith (t =>
{
if(!TryPropigateTaskWithoutResult(t, tcs))
tcs.SetResult (selector(t.Result));
});
return tcs.Task;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment