Skip to content

Instantly share code, notes, and snippets.

@mikehadlow
Created July 9, 2015 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikehadlow/c33802cb6d4d1d2fee81 to your computer and use it in GitHub Desktop.
Save mikehadlow/c33802cb6d4d1d2fee81 to your computer and use it in GitHub Desktop.
Composing monadic async results using Linq
using System;
using System.Threading.Tasks;
namespace Monads
{
public static class AwaitableCompositionSpike
{
/// <summary>
/// First spike composes three aysnc results using Bind(..) extension method
/// </summary>
public async static void Spike()
{
var result = await FirstOperation() // change to FirstOperationFails() to see failure case
.Bind(x => SecondOperation(x)
.Bind(ThirdOperation));
Console.Out.WriteLine(result);
}
/// <summary>
/// Second spike uses Linq syntax to compose the same async results
/// </summary>
public async static void SpikeWithLinq()
{
var result = await
(
from a in FirstOperationFails() // change to FirstOperationFails() to see failure case
from b in SecondOperation(a)
from c in ThirdOperation(b)
select c
);
Console.Out.WriteLine(result);
}
// some async operations ...
public static Task<Result<string>> FirstOperation()
{
return Task.FromResult(Result<string>.Success("Hello!"));
}
public static Task<Result<string>> FirstOperationFails()
{
return Task.FromResult(Result<string>.Fail("I failed!"));
}
public static Task<Result<int>> SecondOperation(string input)
{
return Task.FromResult(Result<int>.Success(input.Length));
}
public static Task<Result<Thing>> ThirdOperation(int length)
{
return Task.FromResult(Result<Thing>.Success(new Thing {Value = length.ToString()}));
}
/// <summary>
/// Monadic Bind
/// </summary>
public static async Task<Result<B>> Bind<A, B>(this Task<Result<A>> a, Func<A, Task<Result<B>>> func)
{
var aResult = await a;
if (!aResult.Succeeded)
{
return Result<B>.Fail(aResult.Message);
}
return await func(aResult.Value);
}
public static Task<Result<T>> ToTaskResult<T>(this T value)
{
return Task.FromResult(Result<T>.Success(value));
}
/// <summary>
/// Linq compatible SelectMany
/// </summary>
public static Task<Result<C>> SelectMany<A, B, C>(
this Task<Result<A>> a,
Func<A, Task<Result<B>>> func,
Func<A, B, C> select)
{
return a.Bind(aval =>
func(aval).Bind(bval =>
select(aval, bval).ToTaskResult()));
}
}
public class Thing
{
public string Value { get; set; }
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents an operation that can succeed or fail
/// </summary>
public class Result<T>
{
public T Value { get; private set; }
public bool Succeeded { get; private set; }
public string Message { get; private set; }
public static Result<T> Success(T value)
{
return new Result<T>
{
Value = value,
Succeeded = true
};
}
public static Result<T> Fail(string message)
{
return new Result<T>
{
Message = message,
Succeeded = false
};
}
public override string ToString()
{
if (Succeeded) return Value.ToString();
return Message;
}
}
}