C# Monads的实现(二)

时间:2023-12-19 09:48:14

再谈continuation monad

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

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

var r = from x in .ToContinuation<int, string>()
from y in .ToContinuation<int, string>()
select x + y; Console.WriteLine(r(z => z.ToString().Replace('', '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 = .ToContinuation<int, string>().SelectMany(
x => .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 = ;
var m = ;
return k(m)(j => (i + j).ToString()).ToContinuation<string, char[]>();
}

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