zoukankan      html  css  js  c++  java
  • 创建我们第一个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

  • 相关阅读:
    Java vs Python
    Compiled Language vs Scripting Language
    445. Add Two Numbers II
    213. House Robber II
    198. House Robber
    276. Paint Fence
    77. Combinations
    54. Spiral Matrix
    82. Remove Duplicates from Sorted List II
    80. Remove Duplicates from Sorted Array II
  • 原文地址:https://www.cnblogs.com/phenixyu/p/5621912.html
Copyright © 2011-2022 走看看