在C#中实现monad的最佳方法是什么?是否有特定的实施策略,或者每个monad的实施策略有所不同?

最佳答案

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

当然,Linq不是火箭科学。比您平均的大学编程课程要多,但仍然如此。它是一系列扩展方法,每个扩展方法都会产生一个占位符(monad),该占位符包含应执行的逻辑以及对其数据源的引用(可以是另一种monadic封装)。您可以(而且确实可以这样做)向基本的Linq库添加其他扩展,以填补功能上的空白或执行满足特定需求的自定义操作。

您还可以创建自己的单子方法链框架,以执行几乎所有操作。几乎所有描述为具有“流畅”编码接口的框架或库都是基于monad的库。有流畅的单元测试声明器,流畅的ORM配置,甚至流畅的域UI映射扩展。

用C#实现monadic库通常是使用静态类和静态方法完成的,它们使用一种或多种monadic类型,这些类型从其预期用途之外是不可访问的。例如,这是一个基本的Monadic库,它执行整数加法和减法:

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


显然,这是非常基本的,所有操作都“急切地”执行。如果这些操作很复杂,则可能不是最佳选择,因此,让我们“懒惰”地执行它们:

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

   public class Monad
   {
      //Change the "value keeper" into a Func that will return the value;
      Func<int> theValue;

      //the constructor now turns the input value into a lambda
      internal Monad(int input) { theValue = ()=>input; }
      //and another constructor is added for intra-class use that takes a lambda
      private Monad(Func<int> input) { theValue = input; }

      //And now the methods will create new lambdas that call the existing lambdas
      public Monad Add(int input){ return new Monad(()=>theValue() + input); }
      public Monad Subtract(int input){ return new Monad(()=>theValue() - input); }

      //Finally, our Value getter at the end will evaluate the lambda, unwrapping all the nested calls
      public int Value { get { return theValue(); } }
   }
}


用法相同,只是在使用代码请求具体值之前不会执行任何操作:

//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;


但是,这有一个问题。这些方法仅采用输入值,并在lambda中对其进行引用。问题在于值的范围取决于包含方法(这意味着它们的寿命不足以评估lambda)。调用Value()getter时,将对lambda进行求值,并将引用所有这些范围外的变量。相反,我们应该将这些值保留在至少与lambda一样长的时间中。 monad是显而易见的选择。这是一个可能的解决方案:

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();


这是单子库的基本模式。开始整个过程​​的静态方法可以是扩展方法,这是Linq使用的样式。方法链的根成为第一个值:

//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);


Linq for objects尤其优雅,因为其库是采用IEnumerable并返回IEnumerable的扩展方法,因此无需任何重载或显式方法调用即可处理转换过程。但是,隐藏可翻译表达式树的IQueryable对象是.NET 3.5中的一个新概念,您必须通过AsQueryable()方法将集合显式转换为IQueryable。

09-11 02:42
查看更多