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>,很明显在本例中,RNullable<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并且慢慢推断出生于的一些规则


乱我心者,今日之日多烦忧