Monads(四)

到目前为止,我们已经知道了如果一个类型遵循Monad Pattern,那么我们就一定可以给其"内部类型(上一篇中是泛型参数)"创造一个包装器。同样的,我们也展示了如何对五种不同的monadic type"加一",并保留其原有语义——可空,惰性等等,本文中让我们尝试推广这些模式到更大的范围

让我们暂时吧目光继续聚焦在int上,高阶函数编程需要一些额外的技巧,所以我决定一点点的去讨论,我们把一个对于int的操作表示为Func<int, int>——接受一个int作为参数并且返回一个int的委托,我们可以使用Func<int, int>来替换我们上阶段中在每个AddOne中的"取值——加一——再包装"操作并将其泛化:

    static Nullable<int> ApplyFunction(Nullable<int> nullable, Func<int, int> function)
    {
        if (nullable.HasValue)
        {
            int unwrapped = nullable.Value;
            int result = function(unwrapped);
            return new Nullable<int>(result);
        }
        else
        {
            return new Nullable<int>();
        }
    }

这样,我们就可以吧AddOne函数变成一个特化的用法:

    static Nullable<int> AddOne(Nullable<int> nullable)
    {
        return ApplyFunction(nullable, x => x + 1);
    }

我相信你肯定知道该如何把类似的方法推广到其他四个类型上,而且还能进一步泛化,比如,我们的泛型参数为什么一定要是int呢?

    static Nullable<T> ApplyFunction<T>(Nullable<T> nullable, Func<T, T> function)
    {
        if (nullable.HasValue)
        {
            T unwrapped = nullable.Value;
            T result = function(unwrapped);
            return new Nullable<T>(result);
        }
        else 
        {
            return new Nullable<T>();
        }
    }

再进一步,假设有一个int->double的函数,输入一个int值并将其除以2,再返回对应的double值;根据Monads的定义可以得知,如果我们有一个int->double的函数,我们应该同样也能创建一个Monadic<int>->Monadic<double>的函数1,很明显这次应该使用两个泛型参数(也就是所谓"Monad类型的内部类型"):第一个是转换前的(本例中为int),第二个是转换后的(本例中为double):

    static Nullable<R> ApplyFunction<A, R>(Nullable<A> nullable, Func<A, R> function)
    {
        if (nullable.HasValue)
        {
            A unwrapped = nullable.Value;
            R result = function(unwrapped);
            return new Nullable<R>(result);
        }
        else
        {
            return new Nullable<R>();
        }
    }

剩余四个类型的ApplyFunction则作为一个练习自行实现。

我们已经知道,Monad Pattern的第一原则是"必定存在一个简单的方法,可以将任意T转换为Monadic<T>",并且之前我曾经说过,虽然"把任意Monadic<T>转换为T"看上去也非常简单,但是实际上情形却微妙了不少。事实上,我们可以把TMonadic<T>中取出仅当对于操作T的任何函数都可以应用在Monadic<T>上(原文: What we’ve shown here is that we actually need to get the values back only insofar as is necessary to ensure that any function on the underlying values can be applied to any amplified value,个人理解为比如上方的Func<A, R>可以应用在Nullable<A>上)

本质上我们在这里所做的,就是把A->R的函数转变为一个Monadic<A>->Monadic<R>的函数(把A->R应用在Monadic<A>并产生一个Monadic<R>),这个函数既保留了A->R的语义,也保留了Monadic<A>->Monadic<R>的语义(比如最开始提到的ApplyFunction就是一个把int->int转换为Nullable<int>->Nullable<int>的函数,它既保留了function的内容也保留了Nullable<T>的语义)

那么接下来提出一个问题:你可以写出这样的一个函数:

    static M<R> ApplyFunction<A, R>(M<A> monadic, Func<A, R> function)

并且维持其正确性(同时保留M<A>->M<R>A->R的语义,比如上文中对于Nullable<T>的泛化)吗?这是归纳得到的Monad Pattern的第二原则吗?

答案是否定的,不过我们已经很接近了!下一章节中,我会对ApplyFunction的函数签名做出一个看上去微不足道但是至关重要的改动,来让他变成Monad Pattern的第二点原则


  1. 原文: then we should be able to make an operation on an “amplified” int that produces an “amplified” double,鉴于用"aimplified"实在是容易让人迷惑(这个词具体表达什么意思在第二篇有讲解,基本就是指"扩充这个类型本身的能力",比如Nullable<byte>扩充了byte本身,让他具有了可空的能力,就可以说Nullable<byte>是一个"amplified" byte),这次直接用Monadic(作者所说的"amplified type"就是monadic type的一种描述,详见第一篇),如未指明,下文中所有的"aimplified"都会直接译作"monadic",如果你觉得这种翻译不合适或者有合理的意见,欢迎在评论区提出 ↩︎

行成于思,毁于随