简体   繁体   English

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

[英]What is the best approach to implementing monads in C#

What are the best approaches to implementing monads in C#? 在C#中实现monad的最佳方法是什么? Are there particular implementation-strategies or is every monad differently implemented from the other? 是否有特定的实施策略,或者每个monad的实施策略有所不同?

To answer the question instead of just commenting on it, Linq is probably the foremost method of performing monadic transformations in C#. 为了回答这个问题而不是仅仅评论它,Linq可能是在C#中执行单数转换的最主要方法。 A Linq method chain is nothing more than a lazily-evaluated, ordered set of list-processing operations. Linq方法链无非是一组延迟评估的有序列表处理操作。

Linq, of course, is not rocket science; 当然,Linq不是火箭科学。 more than your average college-level programming course, but still. 比您平均的大学编程课程要多,但仍然如此。 It's a series of extension methods that each produce a placeholder enumerable (the monad) containing the logic they should perform, and a reference to their source of data (which can be another monadic encapsulation). 它是一系列扩展方法,每个方法都会生成一个占位符(monad),该占位符包含应执行的逻辑以及对其数据源的引用(可以是另一种monadic封装)。 You can, and many do, add additional extensions to the basic Linq library to fill holes in the functionality or perform custom actions that meet specific needs. 您可以(而且确实可以这样做)向基本的Linq库添加其他扩展,以填补功能上的空白或执行满足特定需求的自定义操作。

You can also create your own monadic method chain frameworks, to do practically anything. 您还可以创建自己的单子方法链框架,以执行几乎所有操作。 Pretty much any framework or library described as having a "fluent" coding interface is a monad-based library. 几乎所有描述为具有“流畅”编码接口的框架或库都是基于monad的库。 There are fluent unit test asserters, fluent ORM configurations, even fluent domain-UI mapping extensions. 有流畅的单元测试声明器,流畅的ORM配置,甚至流畅的域UI映射扩展。

Implementing a monadic library in C# is usually done using a static class and static methods, which make use of one or more monadic types which are inaccessible from outside their intended usage. 用C#实现monadic库通常是使用静态类和静态方法完成的,它们使用一种或多种monadic类型,这些类型从其预期用途之外是不可访问的。 For instance, here is a basic monadic library that performs integer addition and subtraction: 例如,这是一个基本的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

Obviously this is very basic, and all operations are performed "eagerly". 显然,这是非常基本的,所有操作都“急切地”执行。 If those operations were complex, this may not be optimal, so instead, let's perform them "lazily": 如果这些操作很复杂,则可能不是最佳选择,因此,让我们“懒惰”地执行它们:

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

Same usage, except no operations will be performed until a concrete Value is requested by consuming code: 用法相同,只是在使用代码请求具体值之前不会执行任何操作:

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

However, there's a problem with this. 但是,这有一个问题。 The methods simply take the input values, and reference them in a lambda. 这些方法仅采用输入值,并在lambda中对其进行引用。 The problem is that the values' scope is dependent on the containing method (meaning they don't live long enough for the lambda to be evaluated). 问题在于值的范围取决于包含方法(这意味着它们的寿命不足以评估lambda)。 When the Value() getter is called, the lambda will be evaluated, and all of these out-of-scope variables will be referenced. 调用Value()getter时,将对lambda进行求值,并将引用所有这些范围外的变量。 Instead, we should persist the values in something that will live at least as long as the lambdas do. 相反,我们应该将这些值保留在至少与lambda一样长的时间中。 The monad is the obvious option. monad是显而易见的选择。 Here is a probable solution: 这是一个可能的解决方案:

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

This is the basic pattern for a monadic library. 这是单子库的基本模式。 The static method that starts the whole thing can be an extension method, which is the style Linq uses. 开始整个过程​​的静态方法可以是扩展方法,这是Linq使用的样式。 The root of the method chain becomes the first value: 方法链的根成为第一个值:

//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 is particularly elegant in that its libraries are extension methods that take an IEnumerable and return an IEnumerable, so the conversion process is handled without any overloading or explicit method calls. Linq for objects尤其优雅,因为其库是采用IEnumerable并返回IEnumerable的扩展方法,因此无需任何重载或显式方法调用即可处理转换过程。 However, IQueryable objects, which hide translatable expression trees, ARE a new idea in .NET 3.5, and you DO have to explicitly convert a collection to an IQueryable, via the AsQueryable() method. 但是,隐藏可翻译表达式树的IQueryable对象是.NET 3.5中的一个新概念,您必须通过AsQueryable()方法将集合显式转换为IQueryable。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM