Monads(六)
在上一篇中我们终于探索出了Monad Pattern真正的规范,也就是在C#中,一个Monad应该是一个"扩充了"其泛型参数功能的泛型类型Monadic<T>
,对于所有的Monadic<T>
,都存在一个简单的方法,可以将T
转换为Monadic<T>
,我们将其表达为如下的函数:
static Monadic<T> CreateSimpleM<T>(T t)
并且如果有一个形如f: A->Monadic<R>
的函数,则同样会有一个形如f: Monadic<T>->Monadic<R>
的函数。我们用如下的C#函数来表示它:
static Monadic<R> ApplySpecialFunction(Monadic<A> wrapped, Func<A, Monadic<R>> function)
然而这两点并非Monad Pattern的全部规范。这两个函数需要一些额外的限制才能够保证正常运行。特别的,我们可以把第一个函数当做包装一个值的函数,而第二个函数知道如何"取出"这个值,这么看来,要求这两个操作保留其内部的值是非常合理的。有了这一点之后,可以注意到ApplySpecialFunction
的第二个参数是一个函数类型f: A->Monadic<R>
,并且A
和R
都是未限定的,可以是任意的A
,也可以是任意的R
,但是CreateSimpleM
却是从T
到Monadic<T>
,因此可以进一步得出一个结论:CreateSimpleM
也许可以作为ApplySpecialFunction
的参数,例如之前提到过的如下代码:(从现在开始我不会再用冗长的方式去写这些函数了)
static Nullable<T> CreateSimpleNullable<T>(T t)
{
return new Nullable<T>(t);
}
static Nullable<R> ApplySpecialFunction<A, R>(Nullable<A> nullable, Function<A, Nullable<R>> function)
{
return nullable.HasValue ? function(nullable.Value) : new Nullable<R>();
}
注意到CreateSimpleNullable
的函数签名与ApplySpecialFunction
的第二个参数是相吻合的,因此可以这么写:
Nullable<int> original = Whatever();
Nullable<int> result = ApplySpecialFunction(original, CreateSimpleNullable);
让我们来一步一步了解这里发生了什么:如果original
是null
,那么ApplySpecialFunction
就简单的返回一个新的Nullable<int>
,而如果original
有一个值,比方说是12,那么就会将这个值从original
里"取"出来,传给CreateSimpleNullable
,然后就得到了一个新的被包装进Nullable<int>
的12
,这里引申出一条新的规范:
对一个monad值执行一个"简单的包装操作"应当产生一个和原来一模一样的monad值
在上面的例子里我们保证了值的一致性。现在,应当注意到我们并不需要"引用一致性"。考虑之前提到过的一种monad类型OnDemand<T>
:
static OnDemand<T> CreateSimpleOnDemand<T>(T t)
{
return () => t;
}
static OnDemand<R> ApplySpecialFunction<A, R>(OnDemand<T> onDemand, Func<A, OnDemand<R>> function)
{
return () => function(onDemand())();
}
假设有:
OnDemand<int> original = () => DateTime.Now.Seconds;
OnDemand<int> result = ApplySpecialFunction(original, CreateSimpleOnDemand);
很明显original
和result
在引用上是不一致的,但是他们的语义相同:调用时会返回的当前的秒数。后者也许要多那么几个步骤来执行,但是总而言之,这两者之间并没有什么不同,一些Monad Pattern的实现中可以轻松的以非常低的代价做到引用一致性,虽然说这是一件很棒的事情,但是实际上我们只需要原来的值和经过SimpleCreateM
变换之后的值保证语义一致就可以了。
下一个限制是如何精确的表达"对一个值的包装",我们可以通过上方及其之前提到的CreateSimpleM
和ApplySpecialFunction
轻松做到这点,回想一下我们之前的SafeLog
函数:
static Nullable<double> SafeLog(int value) { ... }
int original = 123;
Nullable<double> result1 = SafeLog(original);
Nullable<int> nullable = CreateSimpleNullable(original);
Nullable<double> result2 = ApplySpecialFunction(nullable, SafeLog);
你可能觉得result1
和result2
应该是同一个值,毕竟,如果Nullable<int>
只是一个简单的对于int
的包装,那么对他应用一个函数应该就和对内部的int
应用有相同的效果。可以把这个规范推广到全体Monad:假设我们有一个T
类型的值,那么对其应用一个函数得到的结果,应当与将这个函数应用给包装了该T
的Monad的结果相同(再次提醒:不需要引用一致,只需要语义一致)
到此为止,让我们再来做一次总结吧。所谓Monad Pattern的规范,就是指一个Monad类型Monadic<T>
需要提供与下列两个函数作用等同的方法:
static Monadic<T> CreateSimpleM<T>(T t) { ... }
static Monadic<R> ApplySpecialFunction<A, R>(Monadic<A> monad, Func<A, Monadic<R>> function) {...}
并且需要保证:
ApplySpecialFunction(someMonadValue, CreateSimpleM)
会产生一个和someMonadValue
语义一致的值,并且
ApplySpecialFunction(CreateSimpleM(someValue), someFunction)
会产生一个和someFunction(someValue)
语义一致的值
小结
遗憾的是,没有,还是有一条规范没有在本文中被提及,不过我保证这是最后一次了,在下一篇文章中,我们会讨论编程的本质,如何用编程解决问题,以及Monad Pattern是如何在这方面大放异彩的。当然,那条最后的规范也会在下一篇中被推导出来
Comments | NOTHING