C# 在C中实现Monad的最佳方法是什么#

C# 在C中实现Monad的最佳方法是什么#,c#,functional-programming,monads,C#,Functional Programming,Monads,在C#中实现monad的最佳方法是什么?是否有特定的实现策略,或者每个单子的实现方式都不同于其他单子?为了回答这个问题,而不仅仅是对它进行评论,Linq可能是在C#中执行单子转换的最重要的方法。Linq方法链只不过是一组经过延迟评估的有序列表处理操作 当然,Linq不是火箭科学;比你的平均大学水平编程课程还多,但仍然如此。这是一系列扩展方法,每个方法都生成一个占位符可枚举(monad),其中包含它们应该执行的逻辑,以及对其数据源的引用(可以是另一个monadic封装)。您可以(而且很多人都可以)

在C#中实现monad的最佳方法是什么?是否有特定的实现策略,或者每个单子的实现方式都不同于其他单子?

为了回答这个问题,而不仅仅是对它进行评论,Linq可能是在C#中执行单子转换的最重要的方法。Linq方法链只不过是一组经过延迟评估的有序列表处理操作

当然,Linq不是火箭科学;比你的平均大学水平编程课程还多,但仍然如此。这是一系列扩展方法,每个方法都生成一个占位符可枚举(monad),其中包含它们应该执行的逻辑,以及对其数据源的引用(可以是另一个monadic封装)。您可以(而且很多人都可以)向基本Linq库添加其他扩展,以填补功能中的漏洞或执行满足特定需求的自定义操作

您还可以创建自己的一元方法链框架,几乎可以做任何事情。几乎所有被描述为具有“流畅”编码接口的框架或库都是基于monad的库。有fluent单元测试Asserter、fluent ORM配置,甚至fluent域UI映射扩展

在C#中实现一元库通常是使用静态类和静态方法完成的,这些静态类和静态方法使用一个或多个从其预期用途之外无法访问的一元类型。例如,这里有一个执行整数加法和减法的基本单子库:

public static class MonadicArithmetic
{
   public static Monad Take(int input) { return new Monad(input); }

   public class Monad
   {
      int theValue;

      internal Monad(int input) { theValue = input; }   

      public Monad Add(int input){ return new Monad(theValue + input); }
      public Monad Subtract(int input){ return new Monad(theValue - result); }
      public int Value { get { return theValue; } }
   }
}

...

//usage
var result = MonadicArithmetic.Take(1).Add(2).Subtract(1).Value; //2
显然,这是非常基本的,所有操作都是“急切地”执行的。如果这些操作很复杂,那么这可能不是最优的,因此,让我们“懒洋洋地”执行它们:

然而,这有一个问题。这些方法只是获取输入值,并在lambda中引用它们。问题是这些值的作用域取决于包含方法(这意味着它们的存在时间不足以计算lambda)。调用Value()getter时,将计算lambda,并引用所有这些范围外变量。相反,我们应该将这些价值观保持在至少和lambda一样长久的东西中。单子是显而易见的选择。下面是一个可能的解决方案:

public static class MonadicArithmetic
{
   public static Monad Take(int input) { return new Monad(input); }

   public class Monad
   {
      //Our value keeper is now a pure function that requires no external closures
      Func<Func<int>, int, int> operation;
      //and we add two new private fields; 
      //a hook to a lambda that will give us the result of all previous operations,
      Func<int> source;
      //... and the value for the current operation.
      private int addend;

      //our constructor now takes the value, stores it, and creates a simple lambda
      internal Monad(int input) { addend = input; operation = ()=>addend; }
      //and our private constructor now builds a new Monad from scratch
      private Monad(Func<int> prevOp, Func<Func<int>, int, int> currOp, int input) 
      { 
          source = prevOp, 
          operation = currOp, 
          addend = input; 
      }  

      //The methods will create new Monads that take the current Monad's value getter,   
      //keeping the current Monad in memory.
      public Monad Add(int input)
      { 
         return new Monad(this.Result, (f,i)=>f()+i, input); 
      }

      public Monad Subtract(int input)
      { 
         return new Monad(this.Result, (f,i)=>f()-i, input); 
      }

      //And we change our property to a method, so it can also 
      //be used internally as a delegate
      public int Result() { return operation(source, addend); }
   }
}

//usage
var operations = MonadicArithmetic.Take(1).Add(3).Subtract(2); 
//There are now 3 Monads in memory, each holding a hook to the previous Monad, 
//the current addend, and a function to produce the result...

...

//so that here, all the necessary pieces are still available.
var result = operations.Result();  

LINQforObjects特别优雅,因为它的库是接受IEnumerable并返回IEnumerable的扩展方法,因此转换过程不需要任何重载或显式方法调用。但是,隐藏可翻译表达式树的IQueryable对象在.NET 3.5中是一个新概念,您必须通过AsQueryable()方法将集合显式转换为IQueryable。

LINQ。(参见示例)这看起来也不错,唉,这只是一个实现策略的示例。真的LINQ()为一个实际的答案欢呼:)然而,我仍然没有在你的回答中看到一些直接的C代码或策略的细节。我认为你所描述的不符合Monad的定义。它似乎只是一个流畅的界面。monad需要实现特定的操作——标识、绑定和返回。Linq是一元的,因为它实现了这些操作——例如,绑定操作是
SelectMany
扩展方法.I diagree。维基百科:“在函数式编程中,monad是一种抽象的数据类型构造函数,用于表示计算(而不是域模型中的数据)。monad允许程序员将动作链接在一起以构建管道,其中每个动作都用monad提供的附加处理规则进行修饰。”。如果这一点成立,那么几乎所有具有“语法结构”(需要使用内部类型来控制有效方法)的流畅接口都可以标记为monadic。在本例中,我将转换为单子(Take)、转换(Add/Subtract)并返回。
//Each call just adds a shell to the nested lambdas
var operation = MonadicArithmetic.Take(1).Add(2).Subtract(1);

...

//HERE's the payoff; the result is not evaluated till the call to Value_get() behind the scenes of this assignment.
var result = operation.Value; 
public static class MonadicArithmetic
{
   public static Monad Take(int input) { return new Monad(input); }

   public class Monad
   {
      //Our value keeper is now a pure function that requires no external closures
      Func<Func<int>, int, int> operation;
      //and we add two new private fields; 
      //a hook to a lambda that will give us the result of all previous operations,
      Func<int> source;
      //... and the value for the current operation.
      private int addend;

      //our constructor now takes the value, stores it, and creates a simple lambda
      internal Monad(int input) { addend = input; operation = ()=>addend; }
      //and our private constructor now builds a new Monad from scratch
      private Monad(Func<int> prevOp, Func<Func<int>, int, int> currOp, int input) 
      { 
          source = prevOp, 
          operation = currOp, 
          addend = input; 
      }  

      //The methods will create new Monads that take the current Monad's value getter,   
      //keeping the current Monad in memory.
      public Monad Add(int input)
      { 
         return new Monad(this.Result, (f,i)=>f()+i, input); 
      }

      public Monad Subtract(int input)
      { 
         return new Monad(this.Result, (f,i)=>f()-i, input); 
      }

      //And we change our property to a method, so it can also 
      //be used internally as a delegate
      public int Result() { return operation(source, addend); }
   }
}

//usage
var operations = MonadicArithmetic.Take(1).Add(3).Subtract(2); 
//There are now 3 Monads in memory, each holding a hook to the previous Monad, 
//the current addend, and a function to produce the result...

...

//so that here, all the necessary pieces are still available.
var result = operations.Result();  
//using an "identity function" to convert to a monad
var operations = 1.AsMonad().Add(2).Subtract(3);

//performing the conversion implicitly from an overload of Add()
var operations = 1.Add(2).Subtract(3);