简体   繁体   中英

What is the best approach to implementing monads in C#

What are the best approaches to implementing monads in C#? Are there particular implementation-strategies or is every monad differently implemented from the other?

To answer the question instead of just commenting on it, Linq is probably the foremost method of performing monadic transformations in C#. A Linq method chain is nothing more than a lazily-evaluated, ordered set of list-processing operations.

Linq, of course, is not rocket science; 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). 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.

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. There are fluent unit test asserters, fluent ORM configurations, even fluent domain-UI mapping extensions.

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. For instance, here is a basic monadic library that performs integer addition and subtraction:

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. 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). When the Value() getter is called, the lambda will be evaluated, and all of these out-of-scope variables will be referenced. Instead, we should persist the values in something that will live at least as long as the lambdas do. The monad is the obvious option. 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. 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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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