【问题标题】:What is the best approach to implementing monads in C#在 C# 中实现 monad 的最佳方法是什么
【发布时间】:2011-05-24 20:34:00
【问题描述】:

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

【问题讨论】:

标签: c# functional-programming monads


【解决方案1】:

为了回答这个问题而不是仅仅评论它,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 特别优雅,因为它的库是采用IEnumerable 并返回IEnumerable 的扩展方法,因此无需任何重载或显式方法调用即可处理转换过程。但是,隐藏可翻译表达式树的 IQueryable 对象是 .NET 3.5 中的一个新想法,您必须通过 AsQueryable() 方法将集合显式转换为 IQueryable。

【讨论】:

  • 为实际答案干杯:) 但是,我仍然没有在您的答案中看到一些直接的 C# 代码或策略细节
  • 我认为您所描述的内容不符合 Monad 的定义。它似乎只是一个流畅的界面。 Monad 需要实现特定的操作——身份、绑定和返回。 Linq 是一元的,因为它实现了这些操作 - 例如,绑定操作是 SelectMany 扩展方法。
  • 我同意。来自维基百科:“在函数式编程中,monad 是一种抽象数据类型构造函数,用于表示计算(而不是域模型中的数据)。Monad 允许程序员将动作链接在一起以构建管道,其中每个动作都是装饰有 monad 提供的附加处理规则。”。如果这成立,那么几乎任何具有“语法结构”(需要使用内部类型来控制有效方法)的流畅接口都可以标记为单子接口。在这种情况下,我转换为 monad (Take)、transform (Add/Subtract) 并返回。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-10
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多