创建我们第一个Monad

上一篇中介绍了如何使用amplified type, 如IEnumerable<T>,如果我们能找到组合amplified type函数的方法,就会更容易写出强大的程序. 我们已经说了很多次函数组合, 听起来又干又硬。函数组合其实就是简单编程,当我们写像下面这样的代码时:

var customer=customerData.GetById(customerId);

var order=customer.CreateOrder();

我就是在使用函数组合,在这个例子里组合了GetById和CreateOrder函数, 所有的程序都是一个函数组合,显示的或者隐式的.

作为程序员我们知道怎么样组合参数和返回值都是unamplified type的函数,这很自然.

这里有两个简单的函数. 他们有相同的函数签名,我会使用函数委托和lambda表达式 ,而不是直接写静态方法:

Func<int,int>add2=x=>x+2;

Func<int,int>mult2=x=>x*2;

组合这两个函数很简单:

Func<int,int>add2mult2=x=>mult2(add2(x));

我们使用新函数:

var r2=add2mult2(5);

Console.Out.WriteLine("r2={0}",r2);

结果输出r2=14

我们一直在做这样的操作,但是如果我们要组合返回amplified type的函数呢。首先我们需要一个amplified type来组合,在这个例子里我们选择最简单的amplified type. 它仅封装了一个值,我们定义它为Identity:

public class Identity<T>
{
public T Value {get;set;}

public Identity(T value)
{
Value=value;
}
}

现在我们创建两个返回amplified type的函数

Func<int,Identity<int>>add2=x=>new Identity(x+2);

Func<int,Identity<int>>mult2=x=>new Identity(x*2);

如果我们用上面同样的方式组合新函数,编译会失败:

Func<int,Identity<int>>add2mult2=x=>mult2(add2(x));

因为add2返回Identity<int>类型,而multi2的参数是int,类型不匹配,所以编译失败。我们当然可以使用Value属性,

Func<int,Identity<int>>add2mult2=x=>mult2(add2(x).Value)

这样可以,但问题是们要组合Identity<T>的任何函数时都要写x.Value.不要忘了Identity<T>是我们可以想到的最简单的amplified type,实际上它一点用也没有。我们如果要使用有用的amplified type,如IEnumerable<T>,Task<T>或者IMightThrowAnException<T>,我们就又回到了要添加很多样板代码的老路上。我们需要去掉x.Value,怎样去做呢?

我们要得到的是一个函数,它组合了add2和mult2, 我们定义这个函数为Bind。看看上面编译失败的例子,我们就知道Bind的签名了。它需要接收add2的返回值,一个Identity<T>类型的值和mult2,一个签名Func<int, Identity<int>>的函数. 为了使Bind更有用,我们定义它为泛型函数,并且是扩展方法:

//a function ‘Bind‘,allows us to compose Identity returning functions

public static Identity<B> Bind<A,B>(this Identity<A>, Func<A, Identity<B>>func)
{
func(a.Value);
}

当然Bind函数体简单的调用了a.Value并且将结果传给了func. 我们用Bind封装了x.Value. 这样的好处是这种处理amplified type的机制依赖于amplified type封装的类型。Bind的签名是唯一的.

现在我们可以组合add2和mult2了

Func<int,Identity<int>>add2Mult2=x=>add2(x).Bind(mult2);

我们可以使用我们新组合生成的函数:

var r1=add2mult2(5);

Console.Out.WriteLine("r1.Value={0}",r1.Value);

输出r1=14

让我们创建另一个有用的扩展方法, ToIdentity, 为了创建一个新的Identity:

public static Identity<T>ToIdentity<T>(T value)
{
return new Identity<T>(value);
}

现在我们可以用各种Identity值写复杂的表达式:

var result="Hello World!".ToIdentity().Bind(a=>

                                       7.ToIdentity().Bind(b=>

 (new DateTime(2010,1,11)).ToIdentity().Bind(c=>

(a+", "+b.ToString()+", "+c.ToShortDateString())

                                           .ToIdentity())));

Console.WriteLine(result.Value);

输出Hello World!, 7, 11/01/2010

这里我们接收一个string ,"Hello World!",把它转换成一个Identity<string>.将整数7转换为Identity<int>, 日期转换为Identity<DateTime>.我们用Bind函数巧妙的访问封装的值, 连接它们并返回一个Identity<string>.

上面的code不是你见过的最优美的, 但是再看一下,我们接收了不同类型的amplified type, 不用访问Value 属性就可以对它们进行操作, 这是关键. 记住两个事实:首先在未来我们可以去掉lambda表达式,其次Identity<T>可能是最简单的amplified type了. 我们可以实现任意复杂度的功能.

Identity<T>是一个Monad,Monad要求有一个type constructor(Identity<T>自己 )和连个方法:我们的Bind和ToIdentity, 这就是全部。你会发现Bind和ToIdentity在不同语言里有不同的名字。ToIdentity在Haskell中 被叫做return,Bind在C#里被叫做SelectMany, 稍后会看到。他们叫什么名字并不重要,重要的是签名正确:

Bind: Func<Identity<A>,Func<A,Identity<B>>,Identity<B>>

ToIdentity:Func<T,Identity<T>>

Bind是我们写Monad功能的地方, 并且是唯一的地方,这是非常强大的抽象,也是Monad最有魅力的地方.

下一篇我们会看到怎样把Bind重命名为SelectMany, 并与ToIdentity组合使用,使我们可以在Linq中使用Identity<T> Monad

时间: 2024-10-24 10:35:47

创建我们第一个Monad的相关文章

OrmLite动态创建表,一个实体类创建多张表的的偏招

在做一个Android的项目,因为使用数据库频繁,实体字段也比较多,于是打算采用ORM框架,发现OrmLite还不错,于是下了下来,打算使用. 没想到还没正式开工,就遇到问题了.我现在的一个需求如下, 我有一个实体类如下,代表聊天消息,现在要做的是针对每一个当前用户(userId)对应一个朋友(friendId)都要创建一个表.需求比较蛋疼,我本来想的是直接在加两个字段就搞定的,但是我们老大说要分表.没办法只能分表. public class ChatMessage{ public ChatMe

创建你第一个SharePoint 2010 应用程序----完整推荐总结

创建你第一个SharePoint 2010 应用程序 本文中,你会学到: 1. 创建解决方案,使用服务器端对象模型和可视Web Part部件读取和写入列表数据. 2. 使用Visual Studio 2010 创建并部署解决方案. 3. 使用Chart Web Part呈现列表数据. 4. 在一个Web部件页面集成解决方案中的不同可视Web部件. 最终效果: 准备: 首先要创建两个必要的列表Customer Sales和Total Sales.第一个列表储存关于公司和FY10季度销售信息.它包含

最全Pycharm教程(9)——创建并运行一个基本的Python测试程序

最全Pycharm教程(1)——定制外观 最全Pycharm教程(2)——代码风格 最全Pycharm教程(3)——代码的调试.运行 最全Pycharm教程(4)——有关Python解释器的相关配置 最全Pycharm教程(5)——Python快捷键相关设置 最全Pycharm教程(6)——将Pycharm作为Vim编辑器使用 最全Pycharm教程(7)——虚拟机VM的配置 最全Pycharm教程(8)——Django工程的创建和管理 1.主题 这里我们着重介绍Pycharm如何帮助我们创建并

C#创建、安装一个Windows服务

C#创建.安装一个Windows服务http://blog.csdn.net/yysyangyangyangshan/article/details/10515035 关于WIndows服务的介绍,之前写过一篇:http://blog.csdn.net/yysyangyangyangshan/article/details/7295739.可能这里对如何写一个服务不是很详细.现在纯用代码的形式介绍一下windows服务是如何开发和安装的. 开发环境:Win7 32位:工具:visualstudi

如何通过命令行创建和设置一个MySQL用户

我想要在MySQL服务器上创建一个新的用户帐号,并且赋予他适当的权限和资源限制.如何通过命令行的方式来创建并且设置一个MySQL用户呢? 要访问一个MySQL服务器,你需要使用一个用户帐号登录其中方可进行.每个MySQL用户帐号都有许多与之相关连的属性,例如用户名.密码以及权限和资源限制."权限"定义了特定用户能够在MySQL服务器中做什么,而"资源限制"为用户设置了一系列服务器资源的使用许可.创建或更新一个用户涉及到了对用户帐号所有属性的管理. 下面展示了如何在L

在第一战中,我们创建了第一个App project

前情提要(Previously) 在第一战中,我们创建了第一个App project, Hello World,并分析了项目结构,及运行原理. 链接在此:Android Studio 单刷<第一行代码>系列 01 —— 第一战 HelloWorld 摘要(Abstract) 必备技能,使用日志工具 LogCat,并尝试解决真机调试没有日志的问题. 日志工具(LogCat) 日志在任何项目的开发过程中都会起到非常重要的作用,在 Android 项目中如果你想要查看日志则必须要使用 LogCat

从Xen Host Kernel复制出一个半虚拟化的Guest 创建并启动一个xen半虚拟化PV客户机

在< 创建并启动一个xen半虚拟化PV客户机 >一文中讲解了如何从Ubuntu的http源安装半虚拟化的xen guest OS,核心思想是先下载客户机的vmlinuz和initrd,启动xen虚拟机后将会选择ubuntu网络源,进行在线安装.后来准备做几个guest副本时,发现这种在逻辑卷中安装的guest好像难以复制(暂时不知道,主要是不知道如何mount这个逻辑卷,因为它又被分成了Linux和Swap格式),看如下图,该虚拟机使用的一个lv_domU2: 为了能够方便复制出多个半虚拟化g

创建你第一个SharePoint 2010 应用程序

创建你第一个SharePoint 2010 应用程序 本文中,你会学到: 1. 创建解决方案,使用服务器端对象模型和可视Web Part部件读取和写入列表数据. 2. 使用Visual Studio 2010 创建并部署解决方案. 3. 使用Chart Web Part呈现列表数据. 4. 在一个Web部件页面集成解决方案中的不同可视Web部件. 最终效果: 准备: 首先要创建两个必要的列表Customer Sales和Total Sales.第一个列表储存关于公司和FY10季度销售信息.它包含

weblogic管理2 - 创建并启动一个managed server

创建一个managed server. 1.  进入网页console管理页面,如:http://10.100.25.14:7001/console     , 先点击->服务器 (红色标记框),再点击->锁定并编辑  . 2.   点击-> 新建 3. 填好相关内容 (注意端口不要与admin server冲突) , 点击-> 下一步 4.  点击-> 完成 *********至此一个managed server已经创建完成. -----------------------