上一篇中介绍了如何使用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

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

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

    我想要在MySQL服务器上创建一个新的用户帐号,并且赋予他适当的权限和资源限制.如何通过命令行的方式来创建并且设置一个MySQL用户呢? 要访问一个MySQL服务器,你需要使用一个用户帐号登录其中方可 ...

  2. 一个Monad的不严谨介绍

    一个单子(Monad)说白了不过就是自函子范畴上的一个幺半群而已,这有什么难以理解的?* 之前了解了下Monad,后来一段时间没碰,最近研究Parser用到Monad时发现又不懂了.现在重新折腾,趁着 ...

  3. 创建你的一个composer包

    如何创建自己的一个composer包,这个其实很好解决的!只要你了解composer相关的知识便不难做到. 首先,你还不知道什么是composer的话,请先学习下composer的相关知识.简单的说, ...

  4. 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...

  5. jQuery的DOM操作实例(3)——创建节点&&编写一个弹窗

    一.原生JavaScript编写弹窗 二.jQuery编写弹窗 知识点归纳总结: 在原生JavaScript中,创建一个节点: var oDiv=document.createElement(&quo ...

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

    创建一个managed server. 1.  进入网页console管理页面,如:http://10.100.25.14:7001/console     , 先点击->服务器 (红色标记框) ...

  7. weblogic管理1——创建 和 删除一个domain

    说明本文环境  WLS_HOME=/home/weblogic/Oracle/Middleware创建一个domian   第一种方法通过console 创建>[weblogic@11g Mid ...

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

    关于WIndows服务的介绍,之前写过一篇: http://blog.csdn.net/yysyangyangyangshan/article/details/7295739.可能这里对如何写一个服务 ...

  9. range() 函数创建并返回一个包含指定范围的元素的数组

    语法 range(first,second,step) 参数 描述 first 必需.规定数组元素的最小值. second 必需.规定数组元素的最大值. step 可选.规定元素之间的步进制.默认是 ...

随机推荐

  1. bootstrap 模态框日期控件datepicker被遮住问题的解决

    找到日期输入框,并将  .class 属性的 z-index 改大 在JSP页添加样式: 这样就OK了:

  2. PS学习列表

    1 去水印 祛痘 祛斑 2 新建画布,素材拖到ps中,图层 3 钢笔抠图,直线点,圆弧拖,遇到拐角按alt,ctrl+回车键将扣的图变为选区,ctrl+j复制一层上来 4 证件照换底

  3. 【剑指Offer】11、二进制中1的个数

      题目描述:   输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示.   解题思路:   本题有以下两个解决方案:   (1)依次判断每一位.判断的方法是先与1相与,为1则说明该位为1 ...

  4. grpc-web与react的集成

    很久没写总结了,在这里跟大家分享一下自己踩的坑,同时也方便自己多记忆下. 大致流程: 使用create-react-app脚手架生成react相关部分,脚手架内部会通过node自动起一个客户端,然后和 ...

  5. python爬虫09 | 上来,自己动 !这就是 selenium 的牛逼之处

    作为一个男人 在最高光的时刻 就是说出那句 之后 还不会被人打 ... 虽然在现实生活中你无法这样 但是在这里 就让你体验一番 那种呼风唤雨的感觉 我们之前在爬取某些网站的时候 使用到了一些 pyth ...

  6. Git 基础教程 之 分支管理及策略

    创建一个属于自己的分支,别人看不到,你在你自己的分支上干活, 想提交就提交,直至开发完毕后,再一次性合并到原来分支上.这样,既安全,又不影响他人工作.          在实际的开发过程中,应照几个基 ...

  7. Junit在SSH中的集成测试

    测试Spring容器 在Junit的测试类中,继承AbstractJUnit4SpringContextTests就可以进行Spring容器测试, 例如下面测试用例, @RunWith(SpringJ ...

  8. oracle regexp_like介绍和例子

    oracle regexp_like介绍和例子 学习了:http://www.cnblogs.com/einyboy/archive/2012/08/01/2617606.html ORACLE中的支 ...

  9. 为Android开发人员定制的搜索引擎

    我在谷歌上定制了一个专门针对Android开发人员的搜索引擎.载入慢的童鞋考虑FanQiang吧,作为技术人员使用Google才是王道. 在此推荐给大家:cx=01590883735180208228 ...

  10. Android - 找到当前类的Context

    找到当前类的Context 本文地址: http://blog.csdn.net/caroline_wendy 假设是在onContinueCreate或onCreate中, 直接使用this, 就代 ...