在上一篇, 我们创建了第一个Monad,Indentity<T>, 它可能是最简单的Monad, 使我们可以快速了解Monad的模式,而不用陷入细节。接下来我们创建一个有用的Monad, Maybe Monad.

如你所知,任何引用类型如果没有指向实际的对象,它的值就是null, 空引用经常导致一些问题。在无法返回一个实例时 null通常被用作method的返回值.

如果方法可能返回null, 我就应该check返回值是否为null, 执行一些分支代码. 如果我有一连串的方法调用,某些方法可能返回null,我们的意图很快就会因为null检查变的不清晰。如果能提出null检查将会使代码更清晰.

我们要做两件事:首先使方法可以显示的返回null, 然后提出null 检查. Maybe monad可以使我们完成这两件事. C#包含Nullable<T>类型, 但它只能用于值类型,无法满足我们的需求。这里介绍一个新类型Maybe,它有两个子类,Nothing 表示没有值,Just表示含有一个值

public interface Maybe<T>{}

public class Nothing<T>:Maybe<T>
{
pubic overrid string ToString()
{
return "Nothing"
}
} public class Just<T>:Maybe<T>
{
public T Value{get;set} public Just(T value)
{ Value=value;
} public override string ToString()
{
return Value.ToString();
}
}

重写ToString不是必须的,只是使输出简单些. 你也可以将Maybe实现为一个单一类型,包含一个bool型属性 HasProperty, 但是我更喜欢上面的方式, 更接近于Haskell的风格。

为了让Maybe<T>变成Monad, 我们必须实现ToMaybe和Bind方法。ToMaybe比较简单,我们只需要使用参数创建一个新的Just:

public static Maybe<T> ToMaybe<T>(this T value)
{
return new Just<T>(value);
}

Bind方法更有趣,记住Bind是我们实现Monad行为的地方. 我们要提出null 检查的代码, 所以在Bind的实现中,如果this传入的是Nothing, 我们简单的返回一个Nothing, 只有当它有值时我们才调用第二个函数:

public static Maybe<B>Bind(this Maybe<A>a, Func<A,Maybe<B>>func)
{
var justa= a as Just<A>; return justa==null?new Nothing<B>:func(a.Value);
}

Bind方法像一个回路,在一连串的方法调用中,如果有一个返回Nothing, 调用就会停止, 整串调用返回Nothing

最后我们实现SelectMany以使我们可以用Linq语法. 这次我们用Bind实现SelectMany:

public static Maybe<C>SelectMany<A,B,C>(this Maybe<A>a, Func<A,Maybe<B>>func, Func<A,B,C>select)
{
return a.Bind(aval=> func(aval).Bind(bval=> select(aval,bval).ToMaybe());
}

记住这个模式,一旦我们有了Bind的实现,任何Monad的SelectMany都是这模式实现

现在总结 一下. 这是一个安全的除法方法,它的签名告诉我们它可能不返回int.

public static Maybe<int>Div(this int numerator, int denominator
{
return denominator==0:(Maybe<int>)new Nothing<int>():new Just<int>(numerator/denominator);
}

接着将多个除法操作串起来:

public Maybe<int>DoSomeDivision(int denominator)
{ return from a in 12.Div(denominator) from b in a.Div(2) select b;
} Console.WriteLine(result);

看这块代码,任何地方都没有检查null的逻辑, 我们成功的将它们提出来了,现在我们用即DoSomeDivision和和其他类型一起使用:

var result= from a in "Hello world".ToMaybe()

                   from b in DoSomeDivision(2)

                   from c in (new DateTime(2010,1,14)).ToMaybe()

                    select a+" "+ b.ToString()+" " + c.ToShortDataeString();

Console.WriteLine(result);

仍然没有检查null, 我们混合了int,string 和DateTime。运行后输出结果:

Hello World! 3 14/01/2010

现在如果我们把被除数改为0, 

var result= from a in "Hello world".ToMaybe()

                   from b in DoSomeDivision(0)

                   from c in (new DateTime(2010,1,14)).ToMaybe()

                    select a+" "+ b.ToString()+" " + c.ToShortDataeString();

Console.WriteLine(result);

输出Nothing

看到DoSomeDivision返回的Nothing 如何使后续的操作"短路"了吗。它最终在操作串的结尾返回Nothing.如果用命令式编程实现这样的功能会很痛苦,Maybe可能是最简单但是有用的Monad了,但是我们仍可以看到它如何移除大量样板式的代码

下一篇我们将向前飞跃,创建一个monad 解析器, 展示我们拥有的强大能力.

Monad Maybe的更多相关文章

  1. Functional Programming without Lambda - Part 2 Lifting, Functor, Monad

    Lifting Now, let's review map from another perspective. map :: (T -> R) -> [T] -> [R] accep ...

  2. Atitit 理解Monad attilax总结

    Atitit 理解Monad attilax总结 但函数式编程最大的一个问题是,函数是一个数学抽象,在现实世界中不存在,1 那既然这样就够用了,还要 Monad 干嘛?Monad 的作用在这里就体现出 ...

  3. Scalaz(41)- Free :IO Monad-Free特定版本的FP语法

    我们不断地重申FP强调代码无副作用,这样才能实现编程纯代码.像通过键盘显示器进行交流.读写文件.数据库等这些IO操作都会产生副作用.那么我们是不是为了实现纯代码而放弃IO操作呢?没有IO的程序就是一段 ...

  4. Scalaz(32)- Free :lift - Monad生产线

    在前面的讨论里我们提到自由数据结构就是产生某种类型的最简化结构,比如:free monoid, free monad, free category等等.我们也证明了List[A]是个free mono ...

  5. Scalaz(28)- ST Monad :FP方式适用变量

    函数式编程模式强调纯代码(pure code),主要实现方式是使用不可变数据结构,目的是函数组合(composability)最终实现函数组件的重复使用.但是,如果我们在一个函数p内部使用了可变量(m ...

  6. Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

    中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...

  7. Scalaz(20)-Monad: Validation-Applicative版本的Either

    scalaz还提供了个type class叫Validation.乍看起来跟\/没什么分别.实际上这个Validation是在\/的基础上增加了Applicative功能,就是实现了ap函数.通过Ap ...

  8. Scalaz(19)- Monad: \/ - Monad 版本的 Either

    scala标准库提供了一个Either类型,它可以说是Option的升级版.与Option相同,Either也有两种状态:Left和Right,分别对应Option的None和Some,不同的是Lef ...

  9. Scalaz(18)- Monad: ReaderWriterState-可以是一种简单的编程语言

    说道FP,我们马上会联想到Monad.我们说过Monad的代表函数flatMap可以把两个运算F[A],F[B]连续起来,这样就可以从程序的意义上形成一种串型的流程(workflow).更直白的讲法是 ...

  10. Scalaz(17)- Monad:泛函状态类型-State Monad

    我们经常提到函数式编程就是F[T].这个F可以被视为一种运算模式.我们是在F运算模式的壳子内对T进行计算.理论上来讲,函数式程序的运行状态也应该是在这个运算模式壳子内的,也是在F[]内更新的.那么我们 ...

随机推荐

  1. JSP_内置对象_请求转发和请求重定向的区别

    请求重定向:客户端行为,response.sendRedirect(),从本质上将等同与两次请求,前一次请求request对象不会保存,地址栏的URL地址会改变. 请求转发:服务器行为,request ...

  2. 竞品分析」项目协作管理平台-Teambition和CORNERSTONE--深度体验

    一.分析目的 通过分析2B产品中的团队协作管理软件的对比分析,用于为公司团队协作软件的选型做产考. 二.竞品归属市场概况 2.1.目标用户群及需求 主要面向企业用户,用于解决企业不同地域以及不同职能部 ...

  3. inherit 关键字使得元素获取其父元素的计算值

    它可以应用于任何CSS属性,包括CSS简写 all. 对于继承属性,inherit 关键字只是增强了属性的默认行为,只有在重载(overload)其它规则的时候被使用.对于非继承属性,inherit ...

  4. [转载]查看Linux系统硬件信息实例详解

    linux查看系统的硬件信息,并不像windows那么直观,这里我罗列了查看系统信息的实用命令,并做了分类,实例解说. cpu lscpu命令,查看的是cpu的统计信息. blue@blue-pc:~ ...

  5. [系统资源]port range

    ip_local_port_range 端口范围 sysctl Linux中有限定端口的使用范围,如果我要为我的程序预留某些端口,那么我需要控制这个端口范围, 本文主要描述如何去修改端口范围. /pr ...

  6. python-写入csv 文件

    项目要做一个导出客户信息的功能,需要写入csv: 注意文件写入的方式  例如   write open(‘w’) 从头开始写,之前写的会被替换  write open(‘a’) 则代表追加,文件指针放 ...

  7. Golang - 处理json

    目录 Golang - 处理json 1. 编码json 2. 解码json Golang - 处理json 1. 编码json 使用json.Marshal()函数可以对一组数据进行JSON格式的编 ...

  8. 【2】Django安装

    **万物负阴而抱阳,冲气以为和 ** ——老子<道德经> 我们静下心态,开始我们的Django之旅 本节内容 Django的安装 安装结果验证 了解官方文档 1. 安装Django 我们强 ...

  9. navicat 为表添加索引

    navicat 为表添加索引 分析常用的查询场景,为字段添加索引,增加查询速度. 可以添加单列索引,可以添加联合索引. 右键,设计表中可以查看和添加修改索引! 索引一定要根据常用的查询场景进行添加! ...

  10. mongodb--find高级用法

    链式查询 db.person.find().limit(4).sort({sex:-1}) // sort来说,1 是升序, -1 是降序 尽量不要用mongodb去做一些复杂的运算 分页的写法 ·· ...