• C# Monads的实现(二)


    再谈continuation monad

    上一篇中我们已经介绍了continuation monad,但是这个monad与Identity,Maybe,IEnumerable monads稍微难于理解,故本篇再次讨论。

    首先解决上一篇中最后关于continuation monad的问题,即以下这段代码目前还无法通过编译,

    var r = from x in 7.ToContinuation<int, string>()
            from y in 6.ToContinuation<int, string>()
            select x + y;
    
    Console.WriteLine(r(z => z.ToString().Replace('1', 'a'))); // displays a3

    比较前面Identity monad中SelectMany实现了两个版本,可以发现对于continuation monad,我们需要实现其SelectMany的另一个版本,即除了this指定的参数,还有两个输入参数。

    对比Identity monad中相应的SelectMany实现,容易知道,新增的参数是一个Func 函数,在我们上面这个例子中,这个Func 函数的lambda表达式体就是 x + y,故不难写出SelectMany的函数签名,如下,用(1)标注

    public static K<V, Answer> SelectMany<T, U, V, Answer>(this K<T, Answer> m, Func<T, K<U, Answer>> k, Func<T, U, V> s);                                                                                                 (1)

    这里我们也新增了一个泛型参数V,虽然对我们这个例子来说,T,U,V都是int,Answer是string,但是我们考虑了更一般的情况,故用三个泛型参数T,U,V来表示。

    为了方便,我们首先把之前的SelectMany版本再次写出来,免得到上一篇文章中找寻,

    public static K<U, Answer> SelectMany<T, U, Answer>(this K<T, Answer> m, Func<T, K<U, Answer>> k)
    {
        return (Func<U, Answer> c) => m((T x) => k(x)(c));
    }

    那么,如何实现(1)的SelectMany函数?

    分析:

    返回类型为K<V, Answer>,故函数体可以写成类似如下,

    return m.SelectMany<T, V, Answer>(...);

    这句代码中的SelectMany记为之前我们已经实现了的重载版本,根据这个重载版本,我们知道括号中...部分为一个输入参数,类型为Func<T, K<V, Answer>>,故此时(1)的函数体如下,

    return m.SelectMany<T, V, Answer>((T x) => ...)

    现在,这句代码中的...部分的返回类型为K<V, Answer>,此为最终的返回类型,那如何得到这样一个类型值?

    首先,要得到V类型,只能通过s获得,而s的参数类型为T, U,T类型值已知,为x,还需要一个U类型值,U类型值只能通过k获得,应用k到x上得到K<U, Answer>,对这个K<U, Answer>应用已实现的SelectMany重载版本,即可去包装化K<U, Answer>,如下所示,

    k(x).SelectMany(...)

    为了与最终的返回类型K<V, Answer>对接起来,这里k(x).SelectMany(...)中的输入参数必须为Func<U, K<V, Answer>>,此时(1)的函数体可以写成如下,

    return m.SelectMany<T, V, Answer>((T x) => k(x).SelectMany<U, V, Answer>(y => ...)

    此时,这句代码中...部分的返回类型为K<V, Answer>,而要得到这个类型,此时已经很容易了,将s应用到x和y上,得到类型V的值,将V类型包装成K<V, Answer>即可,而包装可以使用ToContinuation<V, Answer>,故(1)的函数体为,

    return m.SelectMany<T, V, Answer>((T x) => k(x).SelectMany<U, V, Answer>(y => s(x, y).ToContinuation<V, Answer>()));

    此时,可以运行上面的那个LINQ的测试代码了。

    深入理解Continuation Monad

    Continuation Monad用于将内部的计算外置。举例说明,

    var r = 7.ToContinuation<int, string>().SelectMany(
                    x => 6.ToContinuation<int, string>().SelectMany(
                        y => (x + y).ToContinuation<int, string>()));
    
    Console.WriteLine(r(i => i.ToString()));

    很明显,这段测试代码用于将7和6相加,并将和转换为字符串。如果对7和6的和不直接简单的转换为字符串,而是想对和先加1再转换为字符串呢,此时只需要修改最后一句代码中int转换为string的逻辑即可,无需修改第一句程序语句。

    Continuation Monad涉及两个类型之间的关系,实现将这两个类型之间的转换的逻辑实现外置,这与F#中的Continuation Computation类似。

    我们在实现Continuation Monad的函数时,主要是通过函数参数对类型进行转换,直到获得需要的类型,如下面代码为例,用于将类型T转换为Answer类型(其中this所指参数省略),

    public static K<T, Answer> CallCC<T, Answer>(this ...);

    K<T, Answer>的类型参考上一篇文章。

    K<T, Answer>正是用于将类型T转换为Answer类型的委托,那要得到这个返回类型,this所指的参数类型可以是Func<Func<T, K<U, Answer>>, K<T, Answer>>,这个类型函数的输入为一个从T到K<U, Answer>的Func,这个Func的输入必须为T,因为我们的最终目的是要从T转换为Answer,那自然必须提供一个T才行,当然,这个Func还可以是从T到K<T, Answer>,此时this所指参数类型为Func<Func<T, K<T, Answer>>, K<T, Answer>>。这个Func甚至可以是从T到K<T, U>的函数,可以有很多可能,这里仅列出已经提过的几种情况,

    public static K<T, Answer> CallCC<T, U, Answer>(this Func<Func<T, K<U, Answer>>, K<T, Answer>> u);            (1)
    
    public static K<T, Answer> CallCC<T, U, Answer>(this Func<Func<T, K<T, Answer>>, K<T, Answer>> u);            (2)

    public static K<U, Answer> CallCC<T, U, Answer>(this Func<Func<T, K<T, U>>, K<U, Answer>> u);               (3)

    再如第一种情况,this所指的参数类型Func<Func<T, K<U, Answer>>, K<T, Answer>>,这个类型函数的输出类型为K<T, Answer>正好与我们最终的返回类型相同,然而,如果这个类型函数的输出类型与最终输出类型不同呢?比如是K<U, Answer>,如下所示,

    public static K<T, Answer> CallCC<T, U, Answer>(this Func<Func<T, K<U, Answer>>, K<U, Answer>> u);              (4)

    这种情况下,对提供的T,仅能得到K<U, Answer>类型,如果不提供从U到T的转换函数,则我们无法实现CallCC。

    Continuation Monad函数实现

    对第一种情况来说,参数u这个类型函数的输出类型即为我们所要的最终类型,故CallCC实现较为简单,如下

    public static K<T, Answer> CallCC<T, U, Answer>(this Func<Func<T, K<U, Answer>>, K<T, Answer>> u)
    {
        return c => u(x => z => c(x))(c);
    }

    第二种情况更加简单,可以看成是第一种情况的U为T的特殊情况。

    第三种情况,参数u这个类型函数的输出类型也是最终的返回来下,其实现如下,

    public static K<U, Answer> CallCC1<T, U, Answer>(this Func<Func<T, K<T, U>>, K<U, Answer>> u)
    {
        return c => u(t => z => z(t))(c);
    }

    Continuation Monad函数用法

    public void Call()
    {
        Func<Func<int, K<int, string>>, K<string, char[]>> u = U;
        var r = u.CallCC();
        
        Console.WriteLine(r(s => s.ToCharArray()));
    }
    
    private K<string, char[]> U(Func<int, K<int, string>> k)
    {
        var i = 50;
        var m = 2;
        return k(m)(j => (i + j).ToString()).ToContinuation<string, char[]>();
    }

    其中,lambda表达式j => (i + j).ToString()中的j其实是m传入的。

  • 相关阅读:
    考研系列 HDU2241之早起看书 三分
    考研系列 HDU2242之空调教室 tarjan
    HDU5880 Family View ac自动机第二题
    HDU2222 Keywords Search ac自动机第一题
    hiho1514 偶像的条件 lower_bound
    HDU1800 hash+去前导0
    阿里云数据库自研产品亮相国际顶级会议ICDE 推动云原生数据库成为行业标准
    MaxCompute 图计算开发指南
    MaxCompute Mars开发指南
    基于MaxCompute的数仓数据质量管理
  • 原文地址:https://www.cnblogs.com/sjjsxl/p/5139209.html
Copyright © 2020-2023  润新知