在C#中实现monad的最佳方法是什么?是否存在特定的实施策略,或者每个monad是否与另一个实现不同?
答案 0 :(得分:6)
回答这个问题而不仅仅是评论它,Linq可能是在C#中执行monadic转换的最重要的方法。 Linq方法链只不过是一个经过懒惰评估的有序列表处理操作集。
当然,林克不是火箭科学;比你平均的大学水平的编程课程,但仍然。它是一系列扩展方法,每个方法都生成一个占位符可枚举(monad),包含它们应该执行的逻辑,以及对它们的数据源的引用(可以是另一个monadic封装)。您可以(有很多人)向基本Linq库添加其他扩展,以填补功能漏洞或执行满足特定需求的自定义操作。您还可以创建自己的monadic方法链框架,几乎可以做任何事情。几乎所有被描述为具有“流畅”编码接口的框架或库都是基于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进行求值,并引用所有这些超出范围的变量。相反,我们应该将价值观坚持至少与lambdas一样长寿的东西。 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();
这是monadic库的基本模式。启动整个事物的静态方法可以是一个扩展方法,这是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。