异常或C#中的monad

Sim*_*ods 12 c# monads

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

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

我已经看了几个样本 - 可能样本吨,我不太确定如何或如果将其概括为成为一个Either monad - 但我找不到任何不在haskell中 - 并且,不幸的是,我肯定不会grok haskell!

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

Mat*_*ias 15

在学习C#中的Exceptionalmonad时,为了练习,我为自己实现了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);
Run Code Online (Sandbox Code Playgroud)

两个表达式产生相同的结果,只是语法不同.结果将是Exceptional<T>出现的DivideByZeroException属性.第一个示例使用LINQ显示monad的"核心",第二个示例包含不同且可能更具可读性的语法,这说明了以更易理解的方式链接的方法.

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

public class Exceptional<T>
{
    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<T> 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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

单子是通过扩展方法完成ToExceptional<T>()SelectMany<T, U>(),对应于单子的单位和绑定功能:

public static class ExceptionalMonadExtensions
{
    public static Exceptional<T> ToExceptional<T>(this T value)
    {
        return new Exceptional<T>(value);
    }

    public static Exceptional<T> ToExceptional<T>(this Func<T> getValue)
    {
        return new Exceptional<T>(getValue);
    }

    public static Exceptional<U> SelectMany<T, U>(this Exceptional<T> value, Func<T, Exceptional<U>> k)
    {
        return (value.HasException)
            ? new Exceptional<U>(value.Exception)
            : k(value.Value);
    }

    public static Exceptional<V> SelectMany<T, U, V>(this Exceptional<T> value, Func<T, Exceptional<U>> k, Func<T, U, V> m)
    {
        return value.SelectMany(t => k(t).SelectMany(u => m(t, u).ToExceptional()));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public static class Exceptional
{
    public static Exceptional<T> From<T>(T value)
    {
        return value.ToExceptional();
    }

    public static Exceptional<T> Execute<T>(Func<T> getValue)
    {
        return getValue.ToExceptional();
    }
}

public static class ExceptionalExtensions
{
    public static Exceptional<U> ThenExecute<T, U>(this Exceptional<T> value, Func<T, U> getValue)
    {
        return value.SelectMany(x => Exceptional.Execute(() => getValue(x)));
    }
}
Run Code Online (Sandbox Code Playgroud)

一些解释:只要链的一个方法抛出异常,就会执行使用此monad构建的方法链.在这种情况下,不会执行链的更多方法,并且第一个抛出的异常将作为Exceptional<T>结果的一部分返回.在这种情况下HasException,Exception将设置和属性.如果没有Exception发生时,HasExceptionfalse与该Value属性将被设置,包含执行方法链的结果.

请注意,Exceptional<T>(Func<T> getValue)构造函数负责异常处理,并且该SelectMany<T,U>()方法负责区分之前执行的方法是否抛出异常.

  • 没呢还没.但这取决于我的项目环境.我担心只有少数人能够把这些东西放在脑袋里;-) (4认同)
  • 我不确定...正如上面所定义的,它似乎并没有给你带来更多的价值。但如果您使用更多辅助方法来扩展 Exceptional,那就太好了。想象一下扩展方法“OnException”,它将是 catch 块的一个很好的替代品。如果您通常有一系列连续排列的 try/catch 块,这可能会派上用场。仅当您可以将事物链接在一起时,Monad 才有意义(Maybe monad 是这里最好的例子)。 (3认同)
  • 有趣的是,您在“现实世界”中使用过它吗? (2认同)

Mik*_*kov 14

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

public class Either<TL, TR>
{
    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<T>(Func<TL, T> leftFunc, Func<TR, T> rightFunc)
        => this.isLeft ? leftFunc(this.left) : rightFunc(this.right);

    public static implicit operator Either<TL, TR>(TL left) => new Either<TL, TR>(left);

    public static implicit operator Either<TL, TR>(TR right) => new Either<TL, TR>(right);
}
Run Code Online (Sandbox Code Playgroud)

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

要点是

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

我还描述了我们如何使用这种Either类型进行数据验证.


Sea*_*ron 5

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

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

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

这两个库都有很好的文档记录,LaYumba 被用作 Manning 书籍Functional Programming in C# 的基础

  • 只需复制在 LaYumba 项目自述文件中找到的注释即可: *注意:欢迎您通过 NuGet 从您的项目中引用 LaYumba.Functional,并提交带有改进的 PR,但该库的主要目的是教学。如需更成熟的函数库,请考虑 LanguageExt* (2认同)