考虑一下这个基本的maybe 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;
}
它的目的是以简洁的方式处理返回可选值的函数链:
var client = Maybe<int>.Some(1)
.Bind(orderId => GetOrder(orderId))
.Bind(order => GetClient(order.ClientId));
Console.WriteLine(client);
在上述情况中,
GetOrder
和GetClient
都返回一个Maybe<T>
,但对于None
的处理被隐藏在Bind
中。目前为止一切正常。但是,如果我要将
Maybe<T>
绑定到一个async
函数,即一个返回Task<Maybe<T>>
的函数,我该怎么办?例如,以下代码会因为Bind
期望的是Func<T,Maybe<U>>
而不是Func<T,Task<Maybe<U>>>
而导致编译错误:var client = Maybe<int>.Some(1)
.Bind(orderId => GetOrderAsync(orderId))
.Bind(order => GetClientAsync(order.ClientId));
Console.WriteLine(client);
我试图在传递给Bind
的lambda内部使用await
Task
,但这迫使我添加了一个重载函数来接受返回Task
的函数:
public Maybe<U> Bind<U>(Func<T, Task<Maybe<U>>> f)
=> HasValue ? f(value).Result : Maybe<U>.None;
如您所见,代码不再使用async
,而是使用Result
进行阻塞。不太好。
第二次尝试是在新的Bind
中await
任务:
public async Task<Maybe<U>> Bind<U>(Func<T, Task<Maybe<U>>> f)
=> HasValue ? await f(value) : Maybe<U>.None;
但是现在Bind
必须将Maybe<T>
包装在Task
中,链式调用看起来很丑陋:
var asyncClient = await (await Maybe<int>.Some(2)
.Bind(orderId => GetOrderAsync(orderId)))
.Bind(order => GetClientAsync(order.ClientId));
有没有更好的解决办法?
我创建了一个完全可用的示例,以防我在解释中漏掉了一些细节。