65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 进行 Monad 类编程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2013 年 9 月 18 日

CPOL

10分钟阅读

viewsIcon

40164

downloadIcon

231

C# 中一个 Haskell monad/(applicative)functor 类的接口,它扩展了 IEnumerable。

介绍 

几个月前,我上了一堂关于 Haskell 函数式编程的课。从那时起,我越来越沉迷于函数式思维方式。目前我经常使用 C# 编程(私人项目,工作)。我开始越来越多地使用 lambda 和委托等函数式特性。

我很好奇能否在 C# 中实现 Haskell 中的 Monads 和 (Applicative)Functors。我只是想玩玩 C#、函数式编程和泛型。结果我发现了一些非常酷的东西,我想分享出来。

当我谷歌搜索 C# 和 monads 时,我发现的关于函数式编程的内容是一些独立的 Maybe 或 Identity 实现,或者是一些 `IEnumerable` 的扩展。但我想到了一些更通用的东西。

起初,我对 `IEnumerable` 及其实现细节知之甚少。我只知道 Linq,并且知道它是由一位(传奇的)Haskell 程序员在考虑 monads 的想法下创建的。

但我仍然发现了一些在 IEnumerable 接口中没有的有用或至少复杂的函数定义。我将所有 Monad 连接到 `IEnumerable` 和 LINQ 语法。(for..in...、where、select)。

基本上,在 IEnumerable 接口中,只有接受一个函数作为参数的函数。例如,没有接受一个函数列表的函数。也许这不是 `IEnumerable` 在创建时的意图。或者它并不是真正需要的,因为通常人们使用 Linq 语法(for..in..)而不是直接调用函数。

包含更详细描述的完整项目可以在 github 上找到:

https://github.com/Muraad/Monad-CSharp 

你会看到什么?

大量的泛型和委托/Func/lambda。实现 IEnumerable 并连接到 LINQ。以及运算符重载。

我所做的事情

我定义了一个名为 IMonad 的接口,它扩展了 IEnumerable,并添加了一些关于 Monad 的有用且复杂的函数。这些函数我在 IEnumerable 中没有找到。

最大的不同是 IEnumerable 通常只用于列表类类型,而不用于内部只有一个值的类型。

因此,我定义了该接口并实现了 Maybe、Identity、ListMonad 和 Either Monad。

背景  

什么是 Monad 和 (Applicative)Functor?

基本上都是一样的。它就像一个可以包含一个或多个值的盒子。并且可以对这些值进行操作。例如,一个函数可以映射到它上面。它可以与另一个盒子中的值一起压缩。列表(或者更好地说是 IEnumerable)是 Monad 的最佳示例,因为每个人都知道这是一种类型(一个盒子),可以包含其他值(这个盒子)。并且你可以为列表定义函数,而无需了解列表中内部的类型。

什么是 Maybe?

Maybe<T> 是一个类(盒子),它可以包含一个类型为 T 的值,也可以什么都没有。它类似于 C# 中的 nullable。

使用代码

我创建了一个 IMonad<A> 接口及其一些实现:Maybe<A>、Identity<A>、ListMonad<A> 和 Either<R, L>。

我将在这里只解释 IMonad,并给出一些使用 Maybe 和 ListMonad 类的例子。

IMonad 接口

public interface IMonad<A> : IEnumerable<A>
{
 
    #region IMonad_Core_Interface_Function_Definitions
 
    IMonad<B> Fmap<B>(Func<A, B> function);
 
    IMonad<A> Pure(A parameter);
 
    A Return();
 
    IMonad<B> App<B>(IMonad<Func<A, B>> functionMonad);
    IMonad<B> App<B>(IMonad<Func<A, IMonad<B>>> functionMonad);
 
    IMonad<C> Com<B, C>(Func<A, B, C> function, IMonad<B> mOther);
    IMonad<C> Com<B, C>(Func<A, B, IMonad<C>> function, IMonad<B> mOther);
    IMonad<C> Com<B, C>(IMonad<Func<A, B, 
                    C>> functionMonad, IMonad<B> mOther);
    IMonad<C> Com<B, C>(IMonad<Func<A, B, 
                    IMonad<C>>> functionMonad, IMonad<B> mOther);
 
    IMonad<A> Visit(Action<A> function);
    IMonad<A> Visit<B>(Action<A, B> action, IMonad<B> mOther);
 
    IMonad<A> Concat(IMonad<A> otherMonad);
 
    #endregion
 
      
    #region Linq_Enumerable_Connection
 
    IMonad<A> Where(Func<A, bool> predicate);   // filter.
    IMonad<A> Where(Func<A, int, bool> predicate);
 
    IMonad<B> Select<B>(Func<A, B> f);       // fmap
    IMonad<B> Select<B>(Func<A, Int32, B> f);   // fmap with index.

    IMonad<B> SelectMany<B>(Func<A, IMonad<B>> f);
    IMonad<B> SelectMany<B>(Func<A, Int32, IMonad<B>> f);
    IMonad<B> SelectMany<TMonad, B>(Func<A, IMonad<TMonad>> selector, 
                                    Func<A, TMonad, B> function);
    IMonad<B> SelectMany<TMonad, B>(Func<A, Int32, IMonad<TMonad>> selector, 
                                    Func<A, TMonad, B> function);
 
    #endregion
}

IMonad 扩展了 IEnumerable,这意味着每个 monad 都是一个可枚举的,即使它只持有一个值。我发现这对于列表 monad 的 App 和 Com 函数非常有用。因为这样 foreach 就可以用于其他 monad,无论它持有一个值还是多个值。IMonad 接口还定义了每个 Monad 都必须实现的 LINQ 函数。当我玩耍并做一些研究时,我认识到 IEnumerable 几乎是一个 Monad。但是由于 Applicative Functor 也是一个 monad 类的类型,我看到 IEnumerable 中没有定义“App”之类的函数。而且 IEnumerable 通常适用于像集合那样持有一个或多个值的类型。所以我的 IMonad 扩展了 IEnumerable。唯一的区别是一些函数定义被“重写”了,它们将 IMonad 作为参数并返回 IMonad,而不是 IEnumerable。

参照 Haskell 类型类,我开始设计这个接口。随着时间的推移,做了一些修改。例如,IEnumerable 扩展一开始并没有。

函数解释:

Pure

IMonad<A> Pure(A parameter);  

此函数简单地接受一个值并将其放入(新创建的)monad 的最小上下文中。

Return

A Return(); 

返回此 monad 内部的值。这里有一个问题是 ListMonad。因为它包含多个类型为 A 的值。我采取的权宜之计是返回内部列表的头部。这里还有另一个问题是 Maybe。如果有人向 Maybe 请求其内部值而它什么都没有怎么办?作为权宜之计,我返回 Maybe 内部类型的默认值。例如,对于 Nothing<int>,Return() 函数将返回零。Either 在这里也是一个问题。我的 Either 实现基本上是一个装饰器,并实现为 Either<R,L> : Identity<L>, IMonad<R>。对于左值,函数被委托给基类 Identity。IMonad<R> 在 Either 中实现,与 Identity 实现相同。根据输入参数和泛型参数,C# 可以为 IMonad 中定义的每个函数选择正确的函数,除了 Return()。

Fmap

IMonad<B> Fmap<B>(Func<A, B> function); 

基本上,fmap 与 IEnumerable 的简单 Select 相同。它将一个函数映射到此 monad 内部的值。结果由 fmap 打包成一个新的 monad 返回。我开始编写 IMonad 接口,然后才深入了解 LINQ 并认识到 Select 已经存在。但我将其保留在接口中,因为我认为 fmap 在这里是一个更好、更容易理解的名称。在 Haskell 中,它也叫 fmap,并在 functor 类型类中定义。

Fmap 2nd

IMonad<B> Fmap<B>(Func<A, IMonad<B>> function); 

与上面的 Fmap 相同。区别在于该函数返回一个新的 monad。

App

IMonad<B> App<B>(IMonad<Func<A, B>> functionMonad);  

这是一个有趣的函数定义。它来自 Haskell Applicative 类型类。这个函数我在 IEnumerable 中没有找到。它就像 fmap 函数,唯一的区别是应用于 monad 内部值(或多个值)的函数也位于另一个 monad 内部。因此,你可以有一个包含函数的 List(Monad),并用一行代码将其应用于另一个 List(Monad) 中的所有值。或者你可以有一个包装在 Maybe 中的函数,并将其应用于此 monad 内部的值(或多个值)。将函数应用于 monad 内部值的结果将打包成一个新的 monad 返回。

App 2nd

IMonad<B> App<B>(IMonad<Func<A, IMonad<B>>> functionMonad); 

App 函数的第二个版本(如同它在 Haskell 中定义的那样)甚至更有趣。在这里,给定 monad 内部的函数也返回一个 monad。因此,如果有多个结果值,结果会扁平化。如果函数返回类型为 B 的列表(monad),并且此 monad 是类型为 A 的列表(monad),则结果类型也是类型为 B 的列表(monad),而不是类型为 B 的列表(monad)的列表(monad)。内部 monad 始终会被扁平化。此函数有一些特殊之处。它可以“跳出”函数被调用的 monad 类型。这意味着对 Maybe 调用 App 可以返回一个 ListMonad,例如。起初,这似乎有一个问题。如何将类型为 B 的函数结果 monad 内部的值连接起来?如果你在一个 ListMonad 内部,那将没有问题。但是对于只能存储一个值的 Maybe 呢?这里的技巧是使用第一个函数返回的第一个 IMonad。接下来返回的 monad 内部的所有其他值都会连接起来。这就是 IMonad 接口中存在 Concat 函数的原因。这是一个我将在所有与新 monad 作为内部函数结果一起工作的函数中使用的模式。Concat 对于单个 monad 意味着什么,是自定义的。对于 ListMonad,它就是人们所期望的。例如,对于 Maybe,我只是返回一个新的 Maybe,其内部值为从给定 Monad 的 Return() 函数返回的值。

Com

IMonad<C> ZipWith<B, C>(Func<A, B, C> function, IMonad<B> mOther); 

将此 ListMonad 内部的值和作为参数传递的其他给定 IMonad 的所有可能组合放入给定函数中。所有函数结果都放入一个新的 monad 中。

Com 2nd

IMonad<C> Com<B, C>(Func<A, B, IMonad<C>> function, IMonad<B> mOther); 

与上面相同,唯一的区别是函数本身返回 IMonad。因此,结果 IMonad 被展平,其内部的每个值都添加到新的结果 monad 中。

Com 3d

IMonad<C> Com<B, C>(IMonad<Func<A, B, C>> functionMonad, IMonad<B> mOther);

与上面相同,其中给定的函数位于 IMonad 内部。

Com 4th

IMonad<C> Com<B, C>(IMonad<Func<A, B, IMonad<C>>> functionMonad, IMonad<B> mOther);

与 Com 2d 相同,只是 monad 内部的函数本身返回一个 monad。与 App 中使用的过程相同,用于生成结果 monad。这是最复杂的函数。

Visit

IMonad<A> Visit(Action<A> function);

将给定的 Action 应用于此 monad 内部的值。在我的测试中,我发现该函数非常有用。例如,你可以将一个带有 Console.Out.Write... 的 lambda 应用于 monad 内部的值。

Visit 2nd

IMonad<A> Visit<B>(Action<A, B> action, IMonad<B> mOther);

访问此 monad 内部的值与另一个给定 monad 内部的值的每个组合。

Concat

IMonad<A> Concat(IMonad<A> otherMonad); 

将两个 monad 连接在一起。这里没有关于 concat 对于具体 monad 意味着什么的通用定义。

其余的函数定义是为了连接到 LINQ。这样每个 monad 都可以与 Linq 一起使用。这已经可以实现了,因为 IMonad 扩展了 IEnumerable。只要每个 monad 都有一个 Enumerator,就可以对其进行 Linq 查询。但我希望 Linq 函数返回并与 IMonad 配合使用。

Linq 连接

与 IEnumerable 中的定义相同,只是 IEnumerable 被 IMonad 替换。因此,Linq 和 Monad 函数可以混合使用。

这里需要提及的是,当一个 Linq 函数接受一个函数作为参数,并且该函数使用了 IEnumerable 内部当前值的索引时,对于单值 monad,如 Maybe 或 Identity,索引始终为零。

单值和多值 monad

有些 monad 只持有一个值(单可枚举),如 Maybe 和 Identity,而有些 monad 持有一个或多个值,如列表 monad(或每个正常的 enumerable 或集合)。

跳出当前 monad 类型

凡是涉及 Func<...,IMonad<..>> 的函数,都能够“跳出”当前的 monad 类型,这意味着结果 monad 类型可以与使用 Func 的 monad 类型不同。所有其他函数都返回相同类型的 monad。

约定

没有一个 monad 知道另一个 monad。它们都只知道 IMonad 和它们自己。因此,不同 monad 组合之间没有特殊处理。即使结果 monad 类型未知,也要改变它,我们使用了 App 2nd 函数中描述的技巧。简而言之,从第一个给定函数返回的 IMonad 用于与下一个函数结果进行连接。

 

Maybe 游乐场

public static void MaybePlayaround()
{
    // Just 5, use implicit operator for *Maybe* to make a Just directly.
    Maybe<int> justInt = 5;
    Console.WriteLine("A new Just<double>: " + justInt.ToString());
    Maybe<int> nothingInt = 0;      // same as nothingInt = new Nothing<int>();
    Console.WriteLine("A new Nothing<double>: " + nothingInt.ToString());
 
    // justInt = 0; or justInt = new Nothing<int>() would make a Nothing out of the justInt

    Console.WriteLine("A new ListMonad<char>: ");
    var listMonadChar = new ListMonad<char>() { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }
                        .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    Console.WriteLine("A new ListMonad<int>: ");
    var listMonadInt = new ListMonad<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
                        .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    Console.WriteLine("A new ListMonad<double>: ");
    var listMonadDouble = new ListMonad<double>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
                            .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    var intToDoubleFunctin = new Func<int, double>(x => { return x * 0.5; });
    Console.WriteLine("A new Just with a function inside: f(x) = x * 0.5 ");
    var justFunction = new Just<Func<int, double>>(intToDoubleFunctin);
    Console.WriteLine(justFunction.ToString());
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Visits each combination of the Just 5 and the ListMonad<char>" + 
                        "using a lambda and Console.Write inside: ");
    justInt.Visit((i, c) => { Console.Out.Write(i + "" + c + ", "); }, listMonadChar);
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // Outputs: 1a, 1b, 1c, 1d, 1e,

    Console.WriteLine("Same with Nothing<int> will output nothing: ");
    nothingInt.Visit((i, c) => { Console.Out.Write(i + "" + c + ", "); }, listMonadChar);
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Visits each combination of the Just 5 and the ListMonad<int> \n" +
                        "using a lambda and Console.Write inside. Add both values in print out: ");
    justInt.Visit((x, y) => { Console.Out.Write( x + y + ", "); }, listMonadInt);
 
    Console.WriteLine("\nSame with Nothing<int>:");
    nothingInt = (Maybe<int>)nothingInt
                    .Visit((x, y) => { Console.Out.Write(x + y + ", "); }, listMonadInt);
    Console.WriteLine(nothingInt.ToString());
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
 
    Console.Write("Fmap f(x) = x * 0.5 over the Just<int>(5): ");
    var justDouble = justInt.Fmap(intToDoubleFunctin).Visit((x) => { Console.Out.Write(x + "\n"); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("App Just<Func>(f(x) = x * 0.5) over the Just<int>(5): ");
    justDouble = justInt.App(justFunction).Visit((x) => { Console.Out.Write(x + "\n"); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("App Just<Func> over the Just<int>(5), \n where the functions returns a new " + 
                    "ListMonad<int>() \n with two times the value inside the Just 5. Output: ");
    var function = new Just<Func<int, IMonad<int>>>((x) => { return new ListMonad<int>(){x, x};});
    var intListMonad = justInt.App(function).Visit( (x) => { Console.Out.Write(x + ", "); } );
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // The result is a ListMonad<int>
    // Output: 5, 5,

    Console.WriteLine("Create a new ListMonad with" + 
       " Func<int, int, double> (x*y, x/y, x%y) inside.");
    Console.WriteLine("Combinate Just 5 and that functions. Result is Just<int>.");
    Console.WriteLine("Only last value is returned because this" + 
       " Com function cannot break out of the Just.");
    Console.WriteLine();
    var functionListMonad = new ListMonad<Func<int, int, double>>();
    functionListMonad.Add( (x, y) => { return x*y;});
    functionListMonad.Add( (x, y) => { return x/ (y==0 ? 1 : y);});
    functionListMonad.Add((x, y) => { return x % (y == 0 ? 1 : y); });
    functionListMonad.Visit((x) => { Console.Out.WriteLine("Func: " + x); });
    var result = justInt.Com(functionListMonad, listMonadInt).Visit((x) => 
        { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // Output: 5

    Console.WriteLine("Create a new ListMonad with \n" +
             "Func<int, int, IMonad<double>> (x+y, x-y, x*y, x/y, x%y) inside.\n" +
             "Where every function packs the result in a new ListMonad<double>.\n" +
             "Combine the Just 5 and the ListMonad<double> with all the functions.\n" +
             "The result ListMonad´s are flattned out, and only one result ListMonad<double> "+
             " with all result values is returned: ");
    Console.WriteLine();
    var functionListMonadTwo = new ListMonad<Func<int, double, IMonad<double>>>();
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>() { x + y }; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>() { x - y }; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x * y}; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x / (y == 0 ? 1 : y)}; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x % (y == 0 ? 1 : y)}; });
    functionListMonadTwo.Add((x, y) => { return new Nothing<double>(); });
    var resultTwo = justInt.Com(functionListMonadTwo, listMonadDouble)
                    .Visit((x) => { Console.Out.Write(x + ", "); });
    // Output: 5*0, 5*1, 5*2,... 5*1, 5/1, 5/2, 5/3, ... 5%1, 5%1, 5%2, 5%3,....
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Do the same with the Nothing<int>: ");
    resultTwo = nothingInt.Com(functionListMonadTwo, listMonadDouble)
                .Visit((x) => { Console.Out.Write(x + ", "); });
    // Output: 5*0, 5*1, 5*2,... 5*1, 5/1, 5/2, 5/3, ... 5%1, 5%1, 5%2, 5%3,....
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("Comb. J(5) and the ListMonad<int> with one function ( f(x,y) = x+y ): ");
    var resultThree = justInt.Com((x, y) => { return x + y; }, intListMonad)
                        .Visit((x) => { Console.Out.Write(x); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("Map a f(x, y) = x*y over the Just 5 and a new Just<int>(10) using LINQ: ");
    var query = from f in new Just<Func<int, int, int>>((x, y) => { return x * y; })
                from x in justInt
                from y in new Just<int>(10)
                select f(x, y);
 
    query.Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
}  

 

ListMonad 游乐场

public static void ListMonadPlayground()
{
    Console.Out.WriteLine("Create two lists [1..5] and [J(1)..(J5)]: ");
    ListMonad<int> listMonadInt = new ListMonad<int>() 
                                    { 1, 2, 3, 4, 5 };
 
    ListMonad<double> listMonadDouble = new ListMonad<double>()
                                        {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
 
    // Because Maybe class has an implicit operator
    // it can be written very cool and easy like a normal list.
    ListMonad<Maybe<int>> listMonadMaybeInt = new ListMonad<Maybe<int>>() 
                                                { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 
    // Functions for Fmap and first App function.
    Func<int, double> intDoubleFunc1 = (x) => { return 0.5 * (double)x; };
    Func<int, double> intDoubleFunc2 = (x) => { return 0.7 * (double)x; };
 
    Console.WriteLine("Fmap f(x) = 0.5 * x over [1,..5,]");
    listMonadInt.Fmap(intDoubleFunc1).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("App [f(x)=0.5*x, f(x)=0.7*x] over [1,..,5]");
    var listMonadintDoubleFunc = 
      new ListMonad<Func<int, double>>(){ intDoubleFunc1, intDoubleFunc2 };
    listMonadInt.App(listMonadintDoubleFunc).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for second App function.
    Func<int, IMonad<double>> intIMonadIntDoubleFunc1 = 
                                (x) => { return new Just<double>(x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc2 = 
                                (x) => { return new Just<double>(x * x *x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc3 = 
                                (x) => { return new Just<double>(x * x * x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc4 = 
                                (x) => { return new Just<double>(x * x * x * x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc5 = 
                                (x) => { return new ListMonad<double>(){x+1, x-1}; };
 
    var listMonadIMonadIntDoubleFunc = new ListMonad<Func<int, IMonad<double>>>();
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc1);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc2);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc3);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc4);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc5);
 
    Console.WriteLine("App [Just(x^2), Just(x^3), Just(x^4), Just(x^5] over [1,..,5]");
    listMonadInt.App(listMonadIMonadIntDoubleFunc).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for combination 
    Func<int, double, double> intDoubleDoubleFunc1 = 
                                (x, y) => { return (double)x + y; };
    Func<int, double, double> intDoubleDoubleFunc2 = 
                                (x, y) => { return (double)x - y; };
    Func<int, double, double> intDoubleDoubleFunc3 = 
                                (x, y) => { return (double)x * y; };
    Func<int, double, double> intDoubleDoubleFunc4 = 
                                (x, y) => { return (double)x / y; };
    Func<int, double, double> intDoubleDoubleFunc5 = 
                                (x, y) => { return (double)x % y; };
 
    var listMonadIntDoubleDoubleFunc = new ListMonad<Func<int, double, double>>()
                                        {intDoubleDoubleFunc1,
                                        intDoubleDoubleFunc2,
                                        intDoubleDoubleFunc3,
                                        intDoubleDoubleFunc4,
                                        intDoubleDoubleFunc5};
 
    Console.WriteLine("Combination with 'normal' result value and function + :");
    listMonadInt.Com(intDoubleDoubleFunc1, listMonadDouble)
                .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Comb. with 'normal' result value and functions [+, -, *, /, %]: ");
    listMonadInt.Com(listMonadIntDoubleDoubleFunc, listMonadDouble)
                    .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for combination with IMonad as result.
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc1 = 
        (x, y) => { return new Just<double>((double)x + y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc2 = 
        (x, y) => { return new Just<double>((double)x - y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc3 = 
        (x, y) => { return new Just<double>((double)x * y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc4 = 
        (x, y) => { return new Just<double>((double)x / y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc5 = 
        (x, y) => { return new ListMonad<double>(){(double)x % y}; };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc6 = 
        (x, y) => { return new ListMonad<double>() { (double)x * y * y, (double) x * y * y * y }; };
            
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc7 = 
        (x, y) => { return new Nothing<double>(); };
 
    var listMonadIntDoubleIMonadDoubleFunc = new ListMonad<Func<int, double, IMonad<double>>>()
                                            {intDoubleIMonadDoubleFunc1,
                                            intDoubleIMonadDoubleFunc2,
                                            intDoubleIMonadDoubleFunc3,
                                            intDoubleIMonadDoubleFunc4,
                                            intDoubleIMonadDoubleFunc5,
                                            intDoubleIMonadDoubleFunc6,
                                            intDoubleIMonadDoubleFunc7};
 
    Console.WriteLine("Combination with IMonad function results.");
    Console.WriteLine("List1[1,..,5], List2[1.0,..,9.0] and function +");
    listMonadInt.Com(intDoubleIMonadDoubleFunc1, listMonadDouble)
                    .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Combination with IMonad function results.");
    Console.WriteLine("List1[1,..,5], List2[1.0,..,9.0] and " +
                        "functions [+, -, *, /, %, [x*y*y, x*y*y*y], Nothing]");
    listMonadInt.Com(listMonadIntDoubleIMonadDoubleFunc, listMonadDouble)
                .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Visit with other IMonad
    Console.WriteLine("Visit with other IMonad and add (+) values in output.");
    listMonadInt.Visit<double>((x, y) => { Console.Write(x * y + ", "); }, listMonadDouble);
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Function applying with Linq: ");
    var query = from f in listMonadIntDoubleDoubleFunc
                from x in listMonadInt
                from y in listMonadDouble
                select f(x, y);
    query.Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("");
    Console.ReadLine();
 
    Console.WriteLine("Function applying with Linq: ");
    var query2 = from f in listMonadIntDoubleIMonadDoubleFunc
                    from x in listMonadInt
                    from y in listMonadDouble
                    select f(x, y);
    query2.Visit((x) => { Console.Write(x.ToString() + ", "); });
    Console.WriteLine("\n");
    Console.ReadLine();
 
} 

运算符重载游乐场

我对 ListMonad 进行了一些运算符重载。

现在 fma、app、combine 和 concat 函数可以通过运算符使用!

public static void ListMonadOperatorPlayground()
{
    int counter = 0;
 
    Console.Out.WriteLine("Create two lists [0..9]: ");
    ListMonad<double> listMonadDouble = new ListMonad<double>() 
                                        { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
 
    ListMonad<double> listMonadDoubleTwo = new ListMonad<double>() 
                                            { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
 
    // Functions for second App function.
    Func<double, IMonad<double>> doubleIMonadDoubleFun1 =
                                (x) => { return new Just<double>(x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun2 =
                                (x) => { return new Just<double>(x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun3 =
                                (x) => { return new Just<double>(x * x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun4 =
                                (x) => { return new Just<double>(x * x * x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun5 =
                                (x) => { return new ListMonad<double>() { x + 1, x - 1 }; };
 
    var listMonadFunc1 = new ListMonad<Func<double, IMonad<double>>>();
    listMonadFunc1.Add(doubleIMonadDoubleFun1);
    listMonadFunc1.Add(doubleIMonadDoubleFun2);
    listMonadFunc1.Add(doubleIMonadDoubleFun3);
    listMonadFunc1.Add(doubleIMonadDoubleFun4);
    listMonadFunc1.Add(doubleIMonadDoubleFun5);
 
    // Functions for combination 
    Func<double, double, double> doubleDoubleDoubleFunc1 =
                                (x, y) => { return (x + y); };
    Func<double, double, double> doubleDoubleDoubleFunc2 =
                                (x, y) => { return x - y; };
    Func<double, double, double> doubleDoubleDoubleFunc3 =
                                (x, y) => { return x * y; };
    Func<double, double, double> doubleDoubleDoubleFunc14 =
                                (x, y) => { return x / y; };
    Func<double, double, double> doubleDoubleDoubleFunc5 =
                                (x, y) => { return x % y; };
 
    var listMonadFunc2 = new ListMonad<Func<double, double, double>>()
                                        {doubleDoubleDoubleFunc1,
                                        doubleDoubleDoubleFunc2,
                                        doubleDoubleDoubleFunc3,
                                        doubleDoubleDoubleFunc14,
                                        doubleDoubleDoubleFunc5};
 
 
    // Functions for combination with IMonad as result.
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc1 =
        (x, y) => { return new Just<double>(x + y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc2 =
        (x, y) => { return new Just<double>(x - y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc3 =
        (x, y) => { return new Just<double>(x * y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc4 =
        (x, y) => { return new Just<double>(x / y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc5 =
        (x, y) => { return new ListMonad<double>() { x % y }; };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc6 =
        (x, y) => { return new ListMonad<double>() { x * y * y, x * y * y * y }; };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc7 =
        (x, y) => { return new Nothing<double>(); };
 
    var listMonadFunc3 = new ListMonad<Func<double, double, IMonad<double>>>()
                                            {intDoubleIMonadDoubleFunc1,
                                            intDoubleIMonadDoubleFunc2,
                                            intDoubleIMonadDoubleFunc3,
                                            intDoubleIMonadDoubleFunc4,
                                            intDoubleIMonadDoubleFunc5,
                                            intDoubleIMonadDoubleFunc6,
                                            intDoubleIMonadDoubleFunc7};
 
    Console.WriteLine("fmap f(x) = x * 0.5 over [1,0..9.0] with \" / \" operator");
 
    var result = (listMonadDouble / ((x) => { return x * 0.5; })).Visit((x) =>
                                                            {
                                                                Console.Out.Write(x + ", ");
                                                                counter++;
                                                                if (counter % 9 == 0)
                                                                    Console.WriteLine("");
                                                            });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("App functions [x^2, x^3, x^4, x^5, [x+1, x-1]] \n" +
                        " over [1,0..9.0] with \" * \" operator");
 
    var resultTwo = (listMonadDouble * listMonadFunc1).Visit((x) =>
                                                            {
                                                                Console.Out.Write(x + ", ");
                                                                counter++;
                                                                if (counter % 9 == 0)
                                                                    Console.WriteLine("");
                                                            });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    // Create a tupel with the ListMonad with functions inside 
    // and with the other ListMonad with double values inside
    // because the \"*\" operator can have only one other argument.
    // it sad there are no way for custom operators in C#
    // and there are no operator that take tree arguments and can be overloaded.
    var funcMonadTupel = new Tuple<IMonad<Func<double, double, double>>,
                                    IMonad<double>
                                    >(listMonadFunc2, 
                                    listMonadDoubleTwo);
 
    counter = 0;
    Console.WriteLine("Combinate [1.0,..,9.0] with [1.0,..,9.0] and functions \n" +
                        " [x+y, x-y, x*y, x/y, x%y]");
    var resultThree = (listMonadDouble * funcMonadTupel)
                        .Visit((x) =>
                        {
                            Console.Out.Write(x + ", ");
                            counter++;
                            if (counter % 9 == 0)
                                Console.WriteLine("");
 
                            if (counter % (9 * 9) == 0)
                                Console.WriteLine("---------------------------------------");
                        });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
 
    var funcMonadTupelTwo = new Tuple<IMonad<Func<double, double, IMonad<double>>>,
                                    IMonad<double>>
                                    (listMonadFunc3,
                                    listMonadDoubleTwo);
 
    var resultFour = (listMonadDouble * funcMonadTupelTwo)
                        .Visit((x) =>
                        {
                            Console.Out.Write(x + ", ");
                            counter++;
                            if (counter % 9 == 0)
                                Console.WriteLine("");
 
                            if (counter % (9 * 9) == 0)
                                Console.WriteLine("----------------------------------------");
                        });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
 
    Console.WriteLine("[1.0,..,9.0] + [1.0,..,9.0] + Just(1000.0) + Nothing \n" +
                        " Fmap -> App -> Com -> Com2nd -> Visit \n" + 
                        " This will take a while!! Are you ready, then press enter :-D :");
    Console.ReadLine();
 
    // Concat both double ListMonad´s to get a bigger list
    // and only to show that its possible 
    // concat a Just(1000.0) and a Nothing<double> to the result list too.
    var resultFive = (listMonadDouble + listMonadDoubleTwo)
                        .Concat(new Just<double>(1000.0))
                        .Concat(new Nothing<double>());
 
    var resultSix = (ListMonad<double>)resultFive;
 
    // This line is done the whole operatione!
    // Without one loop.
    resultSix = resultSix / ((x) => { return x * 100.0; }) * funcMonadTupel *funcMonadTupelTwo;
 
    resultSix.Visit((x) =>
                    {
                        Console.Out.Write(x + ", ");
                        counter++;
                        if (counter % 9 == 0)
                            Console.WriteLine("");
 
                        if (counter % (9 * 9) == 0)
                            Console.WriteLine("-------------------------------------------");
                    });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
} 

结论 

一开始我不知道我会走到哪里。但我喜欢我所做的一切。我更深入地理解了函数式编程,理解了什么是类型,或者如何看待它,以及更多。我甚至看到了只有像 Haskell 这样的真正函数式编程语言才能做到的事情。我阅读了关于 Mozilla 的 Rusty 编程语言的文章和教程。我对这种语言非常感兴趣!

还有很多可以改进的地方。例如,T Return() 可以改为 `void Return(out T)`,以解决 Either 和返回的问题。顺便说一句,到目前为止我还没有测试过 Either 类。我没有遇到编译错误,所以应该会发生一些事情 大笑 | <img src=

历史  

  • 2013 年 9 月 18 日 - 第一个版本发布。
  • 编辑:`Com` 和 `Com2nd` 函数描述有误。结果被打包在一个新的 `IMonad` 中(不总是 `ListMonad`),就像 App 函数所做的那样。
© . All rights reserved.