创建我们第一个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
创建我们第一个Monad的更多相关文章
- 如何通过命令行创建和设置一个MySQL用户
我想要在MySQL服务器上创建一个新的用户帐号,并且赋予他适当的权限和资源限制.如何通过命令行的方式来创建并且设置一个MySQL用户呢? 要访问一个MySQL服务器,你需要使用一个用户帐号登录其中方可 ...
- 一个Monad的不严谨介绍
一个单子(Monad)说白了不过就是自函子范畴上的一个幺半群而已,这有什么难以理解的?* 之前了解了下Monad,后来一段时间没碰,最近研究Parser用到Monad时发现又不懂了.现在重新折腾,趁着 ...
- 创建你的一个composer包
如何创建自己的一个composer包,这个其实很好解决的!只要你了解composer相关的知识便不难做到. 首先,你还不知道什么是composer的话,请先学习下composer的相关知识.简单的说, ...
- 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相对于小傅哥之前编写的字节码编程: ASM.Javassist 系列,Byte Bu ...
- jQuery的DOM操作实例(3)——创建节点&&编写一个弹窗
一.原生JavaScript编写弹窗 二.jQuery编写弹窗 知识点归纳总结: 在原生JavaScript中,创建一个节点: var oDiv=document.createElement(&quo ...
- weblogic管理2 - 创建并启动一个managed server
创建一个managed server. 1. 进入网页console管理页面,如:http://10.100.25.14:7001/console , 先点击->服务器 (红色标记框) ...
- weblogic管理1——创建 和 删除一个domain
说明本文环境 WLS_HOME=/home/weblogic/Oracle/Middleware创建一个domian 第一种方法通过console 创建>[weblogic@11g Mid ...
- C#创建、安装一个Windows服务
关于WIndows服务的介绍,之前写过一篇: http://blog.csdn.net/yysyangyangyangshan/article/details/7295739.可能这里对如何写一个服务 ...
- range() 函数创建并返回一个包含指定范围的元素的数组
语法 range(first,second,step) 参数 描述 first 必需.规定数组元素的最小值. second 必需.规定数组元素的最大值. step 可选.规定元素之间的步进制.默认是 ...
随机推荐
- ionic Plugin插件,与原生app端交互,ionic端代码
创建plugins 目录 definitions.ts文件 definitions.ts文件: import {Plugin} from '@capacitor/core/dist/esm/defin ...
- 关于java中的继承
我们都知道Java中的继承是复用代码.扩展子类的一种方式,继承使得Java中重复的代码能够被提取出来供子类共用,对于Java程序的性能以及修改和扩展有很大的意义,所以这是一个非常重要的知识点. 那么对 ...
- esp32使iOS 获取蓝牙外设的Mac地址
最近在做一个需要上下位机的项目,我负责的任务下位机,使用的主控芯片是esp32.这个项目中有一项是需要手机扫描二维码然后连接作为esp32的蓝牙.二维码中包含了mac地址信息,在手机扫描周围设备的时候 ...
- SSL/TLS 加密新纪元 - Let's Encrypt
转自: https://linux.cn/article-6565-1.html SSL/TLS 加密新纪元 - Let's Encrypt 根据 Let's Encrypt 官方博客消息,Let's ...
- [51Nod 1218] 最长递增子序列 V2 (LIS)
传送门 Description 数组A包含N个整数.设S为A的子序列且S中的元素是递增的,则S为A的递增子序列.如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS).A的LIS可 ...
- Cocos2d+C++运行出现中断的解决方法
原因是引入外部的文件不存在问题,一般是路径问题,例如引入的图片文件路径不存在.
- 亚马逊免费服务器搭建Discuz!论坛过程(三)
参考文章:http://faq.comsenz.com/library/system/env/env_linux.htm 参考但是不要完全按照上述文章,不然很可能出错. 尤其不要init 6 重启,我 ...
- laravel5 文件上传
<!DOCTYPE html><html lang="zh-CN"><head> {{--设置字符编码--}} <meta charset ...
- mybatis源码阅读-Transaction和TransactionFactory(四)
Transaction 类图 接口定义 public interface Transaction { Connection getConnection() throws SQLException; v ...
- plsql developer ini
plsql developer ini [Colors] GradientEnabled=True VerticalGradient=True DefaultGradient=True Gradien ...