• Monad Maybe


    在上一篇, 我们创建了第一个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 解析器, 展示我们拥有的强大能力.

  • 相关阅读:
    调用接口直接下载文件
    Oracle函数简单使用
    JAVA面试题刷题资料
    跨域
    ORACLE JOB
    C# 面试知识点网络文档整理
    GetBuffer 与ToArray区别,解决问题场景
    JQuery选择器分类
    C#判断字符串中含有多少个汉字
    XPATH中text()和string()的使用区别
  • 原文地址:https://www.cnblogs.com/phenixyu/p/5621918.html
Copyright © 2020-2023  润新知