exception或C#中的monad

我在尝试着 神交 初步了解monads。

我有一个数据层调用,其结果我想单独返回结果,例如没有行更新/数据集等,或exception。 我想我需要使用Exception monad,我可以看作是Either monad的一个特例

我看了几个样本 – 可能样本吨,我不太确定如何或如果推广这个成为一个Either monad – 但我找不到任何不在haskell中的东西 – 不幸的是,我肯定不是grok haskell!

我想知道是否有人能指出我的任何样品。

在学习C#中的monad时,为了练习,我为自己实现了一个Exceptional monad。 使用这个monad,您可以链接可能抛出Exception操作,就像在这两个示例中一样:

 var exc1 = from x in 0.ToExceptional() from y in Exceptional.Execute(() => 6 / x) from z in 7.ToExceptional() select x + y + z; Console.WriteLine("Exceptional Result 1: " + exc1); var exc2 = Exceptional.From(0) .ThenExecute(x => x + 6 / x) .ThenExecute(y => y + 7); Console.WriteLine("Exceptional Result 2: " + exc2); 

两个表达式都会产生相同的结果,只是语法不同。 结果将是Exceptional ,并将出现的DivideByZeroException设置为property。 第一个示例使用LINQ显示monad的“核心”,第二个示例包含不同的,可能更具可读性的语法,这说明了以更易理解的方式链接的方法。

那么,它是如何实现的? 这是Exceptional类型:

 public class Exceptional { public bool HasException { get; private set; } public Exception Exception { get; private set; } public T Value { get; private set; } public Exceptional(T value) { HasException = false; Value = value; } public Exceptional(Exception exception) { HasException = true; Exception = exception; } public Exceptional(Func getValue) { try { Value = getValue(); HasException = false; } catch (Exception exc) { Exception = exc; HasException = true; } } public override string ToString() { return (this.HasException ? Exception.GetType().Name : ((Value != null) ? Value.ToString() : "null")); } } 

monad通过扩展方法ToExceptional()SelectMany() ,它们对应于monad的Unit和Bind函数:

 public static class ExceptionalMonadExtensions { public static Exceptional ToExceptional(this T value) { return new Exceptional(value); } public static Exceptional ToExceptional(this Func getValue) { return new Exceptional(getValue); } public static Exceptional SelectMany(this Exceptional value, Func> k) { return (value.HasException) ? new Exceptional(value.Exception) : k(value.Value); } public static Exceptional SelectMany(this Exceptional value, Func> k, Func m) { return value.SelectMany(t => k(t).SelectMany(u => m(t, u).ToExceptional())); } } 

还有一些小辅助结构,它们不是monad核心的一部分:

 public static class Exceptional { public static Exceptional From(T value) { return value.ToExceptional(); } public static Exceptional Execute(Func getValue) { return getValue.ToExceptional(); } } public static class ExceptionalExtensions { public static Exceptional ThenExecute(this Exceptional value, Func getValue) { return value.SelectMany(x => Exceptional.Execute(() => getValue(x))); } } 

一些解释:只要链的一个方法抛出exception,就会执行使用此monad构建的方法链。 在这种情况下,不会执行链的更多方法,并且第一个抛出的exception将作为Exceptional结果的一部分返回。 在这种情况下,将设置HasExceptionException属性。 如果没有发生Exception ,则HasException将为false并且将设置Value属性,其中包含已执行方法链的结果。

请注意, Exceptional(Func getValue)构造函数负责exception处理, SelectMany()方法负责区分以前执行的方法是否抛出exception。

我们在C#解决方案中实现了Either数据结构,我们很高兴使用它。 以下是此类实现的最简单版本:

 public class Either { private readonly TL left; private readonly TR right; private readonly bool isLeft; public Either(TL left) { this.left = left; this.isLeft = true; } public Either(TR right) { this.right = right; this.isLeft = false; } public T Match(Func leftFunc, Func rightFunc) => this.isLeft ? leftFunc(this.left) : rightFunc(this.right); public static implicit operator Either(TL left) => new Either(left); public static implicit operator Either(TR right) => new Either(right); } 

(我们的代码有更多辅助方法,但它们是可选的)

要点是

  • 您只能设置LeftRight
  • 有隐式运算符可以使实例化更容易
  • 模式匹配有一种匹配方法

我还描述了我们如何使用这种Either类型进行数据validation 。

所以 – 不知道是否有人感兴趣 – 在Mike Hadlow领导之后,我已经非常 初步地实现了。 其中一些感觉不太正确,但它是一个开始。 (话虽如此,我不会用它 – 你可能会损失一百万美元,甚至会杀死一个人 – 只是我的警告!)

可以编写的代码类型的一个非常简单的示例是

 var exp = from a in 12.Div(2) from b in a.Div(2) select a + b; Assert.AreEqual(9, exp.Value()); var exp = from a in 12.Div(0) from b in a.Div(2) select b; Assert.IsTrue(exp.IsException()); 

使用Div方法实现如下:

 public static IExceptional Div(this int numerator, int denominator) { return denominator == 0 ? new DivideByZeroException().ToExceptional() : (numerator / denominator).ToExceptional(); } 

要么

 public static IExceptional Div_Throw(this int numerator, int denominator) { try { return (numerator / denominator).ToExceptional(); } catch (DivideByZeroException e) { return e.ToExceptional(); } } 

(直接我可以看到api的潜在改进,但我不确定如何实现它。我认为这

 new DivideByZeroException().ToExceptional() 

如果是的话会更好

 new DivideByZeroException().ToExceptional() 

您稍后会看到我的实现,希望有人能够为上面的内容重新构建它。)

monadic位在这里完成(主要是)

 public static class Exceptional { public static IExceptional ToExceptional(this TValue result) { return new Value(result); } public static IExceptional ToExceptional(this TException exception) where TException : System.Exception { return new Exception(exception); } public static IExceptional Bind(this IExceptional first, Func> func) { return first.IsException() ? ((IInternalException)first).Copy() : func(first.Value()); } public static IExceptional SelectMany(this IExceptional first, Func> func, Func select) { return first.Bind(aval => func(aval) .Bind(bval => select(aval, bval) .ToExceptional())); } } 

主界面指定为

 public interface IExceptional { bool IsException(); TValue Value(); } 

我有一个内部接口,用于获取抛出的exception(更晚)

 internal interface IInternalException { IExceptional Copy(); } 

具体实现如下:

 public class Value : IExceptional { TValue _value = default(TValue); public Value(TValue value) { _value = value; } bool IExceptional.IsException() { return false; } TValue IExceptional.Value() { return _value; } } public class Exception : IInternalException, IExceptional where TException : System.Exception { TException _exception = default(TException); public Exception(TException exception) { _exception = exception; } bool IExceptional.IsException() { return true; } IExceptional IInternalException.Copy() { return _exception.ToExceptional(); } TException GetException() { return _exception; } TValue IExceptional.Value() { return default(TValue); } } 

只是一句解释……对于我来说,最棘手的一点是出现exception时的Bind操作。 如果您正在处理操作管道并且在该过程的早期抛出exception,则需要在管道中保持该exception,以便在表达式完成时返回IExceptional包含先前发生的exception。 这就是IInternalException的原因。 它使我能够创建一个相同或(可能不同)类型的新IExceptional(例如IExceptional – > IExceptional),但是将exception复制到新的IExceptional,而不必知道内部exception的类型。

毫无疑问,有很多改进可能。 例如,我可能会发现您可能想要跟踪IExceptional中的错误堆栈。 可能有多余的代码或更好的方法来达到目的……但……这对我来说意味着一点点学习。

我们将非常感激地提出任何想法/建议。

值得注意的是,现在有可用的C#库包含Either实现:

language-ext库可用于.Net 4.5.1和.Net Standard 1.3

LaYumba库可用于.Net Standard 1.6和.Net Core 1.1。

这两个库都有很好的文档,LaYumba被用作Manning书中C#函数式编程的基础。

C#对monad没有太多的支持(并且LINQforms的支持并不是真正意义上的一般monad),没有内置的Exception或Either monad。 你应该throwexception,然后catch它。