我如何将Monadic绑定到异步函数?

Goo*_*ide 5 c# monads bind async-await

考虑一下monad的基本实现:

public class Maybe<T>
{
    private readonly T value;

    private Maybe(bool hasValue, T value) : this(hasValue) => this.value = value;

    private Maybe(bool hasValue) => HasValue = hasValue;

    public bool HasValue {get;}

    public T Value => HasValue ? value : throw new InvalidOperationException();

    public static Maybe<T> None {get;} = new Maybe<T>(false);

    public static Maybe<T> Some(T value) => new Maybe<T>(true, value);

    public Maybe<U> Bind<U>(Func<T, Maybe<U>> f) => HasValue ? f(value) : Maybe<U>.None;
}
Run Code Online (Sandbox Code Playgroud)

其目的是以干净的方式处理返回可选值的函数链:

var client = Maybe<int>.Some(1)
    .Bind(orderId => GetOrder(orderId))
    .Bind(order => GetClient(order.ClientId));
Console.WriteLine(client);
Run Code Online (Sandbox Code Playgroud)

在上述情况下,两个GetOrderGetClient返回Maybe<T>,但处理的None情况下,被藏在里面Bind。到目前为止,一切都很好。

但是我如何将a绑定Maybe<T>async函数,即函数返回Task<Maybe<T>>呢?例如,以下代码由于编译器错误而失败,因为Bind期望的是a Func<T, Maybe<U>>而不是a Func<T, Task<Maybe<U>>>

var client = Maybe<int>.Some(1)
    .Bind(orderId => GetOrderAsync(orderId))
    .Bind(order => GetClientAsync(order.ClientId));
Console.WriteLine(client);
Run Code Online (Sandbox Code Playgroud)

我试图在传递给的lambda awaitTask内部Bind,但是这迫使我添加了一个Bind接受函数的重载,该函数返回a Task

public Maybe<U> Bind<U>(Func<T, Task<Maybe<U>>> f) 
    => HasValue ? f(value).Result : Maybe<U>.None;
Run Code Online (Sandbox Code Playgroud)

如您所见,该代码不再运行async,而是使用进行阻止Result。嗯

第二次尝试是await在新任务内部Bind

public async Task<Maybe<U>> Bind<U>(Func<T, Task<Maybe<U>>> f) 
    => HasValue ? await f(value) : Maybe<U>.None;
Run Code Online (Sandbox Code Playgroud)

但现在Bind有包裹Maybe<T>在一个Task和链接看起来会比较难看:

var asyncClient = await (await Maybe<int>.Some(2)
    .Bind(orderId => GetOrderAsync(orderId)))
    .Bind(order => GetClientAsync(order.ClientId));
Run Code Online (Sandbox Code Playgroud)

有更好的解决方案吗?

我创建了一个可以正常工作的示例,以防万一我错过了解释中的一些细节。

Goo*_*ide 5

我想我找到了一个很好的解决方案。这个想法是Task<Maybe<T>>用两个Bind函数扩展的,这些函数基本上将等待转发到Maybe<T>链中的第一个函数:

public static class TaskExtensions 
{
    public static async Task<Maybe<U>> Bind<T, U>(
        this Task<Maybe<T>> task, Func<T, Maybe<U>> f) 
        => (await task).Bind(f);

    public static async Task<Maybe<U>> Bind<T, U>(
        this Task<Maybe<T>> task, Func<T, Task<Maybe<U>>> f) 
        => await (await task).Bind(f);
}
Run Code Online (Sandbox Code Playgroud)

有了这些,我们可以将函数绑定到直接Maybe<T>返回Maybe<T>另一个Maybe<T>任务的任务:

// Notice how we only have to await once at the top.
var asyncClient = await Maybe<int>.Some(2)
    .Bind(orderId => GetOrderAsync(orderId))
    .Bind(order => GetClientAsync(order.ClientId));
Run Code Online (Sandbox Code Playgroud)

工作示例:https : //dotnetfiddle.net/Kekp0S