再谈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传入的。

C# Monads的实现(二)的更多相关文章

  1. C# Monads的实现(一)

    了解Haskell语言的朋友都知道它是一门纯函数式编程,输出结果只跟输入参数相关,这导致Haskell不能有输入输出函数,因为不同的环境下,输入相同,但输出可能有所不同.Haskell语言中,变量的值 ...

  2. C# 函数式编程及Monads.net库

    函数式编程中,一切皆为函数,这个函数一般不是类级别的,其可以保存在变量中,可以当做参数或返回值,是函数级别的抽象和重用,将函数作为可重用的基本模块,就像面向对象中一切皆为对象,把所有事物抽象为类,面向 ...

  3. 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态

    最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...

  4. 前端开发中SEO的十二条总结

    一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...

  5. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. 谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  8. MIP改造常见问题二十问

    在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...

  9. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

随机推荐

  1. git commit

    使用 git add 命令将想要快照的内容写入缓存区, 而执行 git commit 将缓存区内容添加到仓库中. Git 为你的每一个提交都记录你的名字与电子邮箱地址,所以第一步需要配置用户名和邮箱地 ...

  2. C语言:SQLITE3的学习

    Sqlite基础学习 一.sqlite的概念 SQLite是一款轻型数据库,是遵守ACID的关系型数据库管理系统,由C语言开发设计.Sqlite的设计目标着眼于嵌入式领域,所以具有占用系统资源低和处理 ...

  3. 常用的HTTP方法

    方法                 描述 是否包含主体 GET  从服务器获得一份文档 否 HEAD  只从服务器获得响应报文的首部 否 POST  向服务器发送需要处理的数据 是 PUT  将请求 ...

  4. 关于Python的文件IO

    使用Open函数, 第一个参数为文件名, 例如"C:\abc.txt",这里要注意的是r"C:\abc.txt". 第二个参数为文件的操作方式, 这里着重探讨写 ...

  5. android app安全问题设置

    1.应用签名未校验风险:检测 App 程序启动时是否校验签名证书. 2.应用数据任意备份风险 Android 2.1 以上的系统可为 App 提供应用程序数据的备份和恢复功能,该 由 AndroidM ...

  6. nginx在window上无法启动的问题

    内容列表: 简要介绍 下载安装 配置测试 一.简要介绍 Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP ...

  7. linux配置更改yum源

    1,进入yum源配置目录 cd /etc/yum.repos.d 2,备份系统自带的yum源mv CentOS-Base.repo CentOS-Base.repo.bk下载163网易的yum源:wg ...

  8. TreeSet与TreeMap的源码分析 JDK7

    TreeSet存储原则是:不可重复,有序的. public TreeSet() { this(new TreeMap<E,Object>()); } public TreeSet(Comp ...

  9. 【c++】size_t 和 size_type的区别

    为了使自己的程序有很好的移植性,c++程序员应该尽量使用size_t和size_type而不是int, unsigned 1. size_t是全局定义的类型:size_type是STL类中定义的类型属 ...

  10. 描述符和property内建函数

    首先我们搞清楚__getattr__ ,__get__ 和 __getattribute__ 作用的不同点. __getattr__在授权中会用到. __getattribute__  当要访问属性时 ...