Monads(五)
我们已经非常接近"Monads Pattern"的真相了!迄今为止,我们明白了对于一个Monadic<T>
,必须有一种简单的方法,能将任意的T
转换为Monadic<T>
,在上一章节中,我们发现了对于任意A
->R
,都可以被应用到Monadic<A>
上用以产生一个Monadic<R>
,并且同时保留A
->R
的语义和Monadic<A>
->Monadic<R>
的语义。看上去我们已经完成了所有的工作,还有什么吗需要注意的呢?
我确实说过你可以把任意一个A
->R
,应用在Monadic<A>
上,并借此产生一个Monadic<R>
作为返回值,问题在于,R
真的可以是除了void
以外的任意类型吗?假设我们有如下的一元函数:
static Nullable<double> SafeLog(int x)
{
return x > 0 ? new Nullable<double>(Math.Log(x)) : new Nullable<double>();
}
看上去这就是一个普通的一元函数,也就是说我们应该可以把它应用在Nullable<int>
上,但是当我们这么做的时候...返回值却变成了Nullable<Nullable<double>>
。我们的ApplyFunction<A, R>
接受一个Nullable<A>
和一个Func<A, R>
,并且返回给你一个Nullable<R>
,很明显在本例中,R
是Nullable<double>
。
请回想一下我之前说过我会忽略掉使得Nullable<Nullable<double>>
不合法的泛型约束,我的观点是,即便这种写法是合法的,看上去也非常的不对劲。类似的,如果我们有拥有一个int
->Lazy<double>
,将其应用给ApplyFunction
就会产生一个不怎么对劲的值Lazy<Lazy<double>>
,同样的,换成Task<int>
结果就是Task<Task<double>>
,在这些例子中可以看出,ApplyFunction
应该具有去掉这些间接包装的能力,让我们稍微改写一下它:
static Nullable<R> ApplySpecialFunction<A, R>(Nullable<A> nullable, Func<A, Nullable<R>> function)
{
if (nullable.HasValue)
{
A unwrapped = nullable.Value;
Nullable<R> result = function(unwrapped);
return result;
}
else
{
return new Nullable<R>();
}
}
很简单是不是?现在可以把SafeLog
应用在一个int
上传递进去并且得到一个Nullable<double>
而非Nullable<Nullable<double>>
了,不过,Nullable<T>
是最简单的monadic type;让我们试着去吧这种模型应用在稍微复杂一点的OnDemand<T>
上面吧!
static OnDemand<R> ApplySpecialFunction<A, R>(OnDemand<A> onDemand, Func<A, OnDemand<R>> function)
{
return () =>
{
A unwrapped = onDemand();
OnDemand<R> result = function(unwrapped);
return result();
}
}
同样是小事一桩,并且我们依然保证了所有的计算都仅仅会在被调用的那一刻被执行,类似模式同样可以以非常简单的方法应用在Lazy<T>
和Task<T>
上:
static Lazy<R> ApplySpecialFunction<A, R>(Lazy<A> lazy, Func<A, Lazy<R>> function)
{
return new Lazy(() =>
{
A unwrapped = lazy.Value;
Lazy<R> result = function(unwrapped);
return result.Value;
};
}
static async Task<R> ApplySpecialFunction<A, R>(Task<A> task, Func<A, Task<R>> function)
{
A unwrapped = await task;
Task<R> result = function(unwrapped);
return await result;
}
看到了吗?一个monadic type总是知道该如何把Monadic<Monadic<R>>
扁平化为Monadic<R>
,避免了诸如Nullable<Nullable<R>>
或者Task<Task<R>>
这样的尴尬情景!至于IEnumerable<R>
,这里有一个小小的挫折:我们该如何把IEnumerable<IEnumerable<R>>
转换为IEnumerable<R>
呢?很简单:
static IEnumerable<R> ApplySpecialFunction<A, R>(IEnumerable<A> sequence, Func<A, IEnumerable<R>> function)
{
foreach(A unwrapped in sequence)
{
IEnumerable<R> result = function(unwrapped);
foreach(R r in result)
yield return r;
}
}
如果你观察的足够仔细,那么你就会发现该ApplySpecialFunction
拥有另一个更常见的名字SelectMany
——LINQ的扩展函数,我们会在该系列的剩余部分来讲解这个有趣且绝非巧合的现象
总结
总而言之,到目前为止,我们已经发现了以下原则:
第一原则
对于任意一个Monadic<T>
,都有一个简单的方法,可以使任意一个T
转换为Monadic<T>
:
static Monadic<T> CreateSimpleMonadic<T>(T value)
第二原则
对于任意一个Monadic<T>
,你都可以将一个形如A
->R
的函数应用在Monadic<A>
上,并且得到一个Monadic<R>
:
static Monadic<R> ApplyFunction<A, R>(Monadic<A> wrapped, Func<A, R> function)
第三原则
对于任意一个Monadic<T>
,你都可以将一个形如A
->Monadic<R>
的函数应用在Monadic<A>
上,并且得到一个Monadic<R>
:
static Monadic<R> ApplySpecialFunction<A, R>(Monadic<A> wrapped, Func<A, Monadic<R>> function)
不过,难道这三条真的就是Monad Pattern的定义了吗?有一个小问题:这三个原则中有一个是重复的——很明显第二条本身是第三条在R == Monadic<R>
的情况下的一个特例,它可以通过第一条和第三条实现!,假设我们有一个CreateSimpleMonadic
和一个ApplySpecialFunction
,那么我们就可以组合起来实现一个ApplyFunction
:
static Monadic<R> ApplyFunction<A, R>(Monadic<A> wrapped, Func<A, R> function)
{
return ApplySpecialFunction<A, R>(wrapped, unwrapped => CreateSimpleMonadic(function(unwrapped)));
}
第二和第三的函数名显然弄反了,前者ApplyFunction
才是后者ApplySpecialFunction
的一个特化例子
因此,我们可以把第二条从列表中去除,从而归纳出新的Monad Pattern规范:
第一原则
有一个这样的方法:
static Monadic<T> CreateSimpleMonadic<T>(T value)
第二原则
有个这样的方法:
static Monadic<T> ApplySpecialFunction<A, R>(Monadic<A> wrapped, Func<A, Monadic<R>> function)
这样就同时保留了Monadic<T>
对于T
所扩充的功能,以及function
本身的语义。另外,ApplySpecialFunction
通常依据这样的泛式运作:
1. 取出被Monadic<A>
包装的一系列值
2. 通过某些方法将这一系列值组合包装为一个单独的Monadic<R>
而取出值与组合包装新的值的实现决定了Monad如何去“扩充”其包装的类型
这里其实有另外一种定义Monad的方法:假设我们有一个
ApplyFunction
,并且有一个能将Monadic<Monadic<R>>
转换为Monadic<R>
的方法,尽管这个方法同样非常的简单,易于理解,但是我们一般来说不会使用这种方式定义Monad,因此在本系列的剩余部分中也不会加以赘述
实际上,这里还有一些额外的需求ApplySpecialFunction
必须遵守,否则就无法正确的实现一个Monad Pattern,下一章节中,我们会继续深入Monad Pattern并且慢慢推断出生于的一些规则
Comments | NOTHING