对象定向程序中的功能原则

时间:2016-09-07 13:38:30

标签: c# functional-programming design-principles

今天我看到了一段代码,我正在考虑它的意义。所以我做了一个小例子,你可以看到它是如何工作的。 据我所知,它应该是大量使用Lambda-Caculus的嵌套功能,就像它在功能语言中使用一样。 因为我发现这是不可理解的(从文件跳转到文件)我只想知道你们中的某个人是否有类似这样的经历。

示例功能一

    public void DoSomethingA(int inputInt, Action<int?, Exception> result)
    {
        try
        {
            int? retVal = inputInt;
            result(retVal, null);
        }
        catch (Exception e)
        {
            result(null, e);
        }
    }

示例功能二

    static void DoSomethingB(int? baseInt, Action<Exception> result)
    {
        try
        {
            result(null);
        }
        catch (Exception e)
        {
            result(e);
        }
    }

示例调用两者。

    public int? CalculateSomething(int input)
    {
        int? result;

        DoSomethingA(input, (resultInt, aException) =>
        {
            if (aException != null)
            {
                return;
            }

            DoSomethingB(resultInt, bException =>
            {
                if (bException != null)
                {
                    return;
                }

                result = resultInt;
            });
        });
        return null;
    }

可能有趣的是,“主要”功能将具有某种承诺。所以它注册了函数并等待结束然后获得结果。

2 个答案:

答案 0 :(得分:1)

你所描述的是Monad。特别是Try monad。我将在下面展示如何实现Try monad,CalculateSomething的代码如下所示:

public Try<int> CalculateSomething(int input) =>
    from x in DoSomethingA(input)
    from y in DoSomethingB(x)
    select y;

任何措施都很容易理解。

首先声明一个代表Try操作的委托:

public delegate TryResult<T> Try<T>();

接下来定义TryResult<T>。它将捕获返回值on-success,以及失败异常on-fail:

public struct TryResult<T>
{
    internal readonly T Value;
    internal readonly Exception Exception;

    public TryResult(T value)
    {
        Value = value;
        Exception = null;
    }

    public TryResult(Exception e)
    {
        Exception = e;
        Value = default(T);
    }

    public static implicit operator TryResult<T>(T value) =>
        new TryResult<T>(value);

    internal bool IsFaulted => Exception != null;

    public override string ToString() =>
        IsFaulted
            ? Exception.ToString()
            : Value.ToString();

    public static readonly TryResult<T> Bottom = new InvalidOperationException();
}

接下来为Try委托定义一些扩展方法(这是委托支持扩展方法的C#的一个鲜为人知的特性):

public static class TryExtensions
{
    public static TryResult<T> Try<T>(this Try<T> self)
    {
        try
        {
            if (self == null) return TryResult<T>.Bottom;
            return self();
        }
        catch (Exception e)
        {
            return new TryResult<T>(e);
        }
    }

    public static R Match<T, R>(this Try<T> self, Func<T, R> Succ, Func<Exception, R> Fail)
    {
        var res = self.Try();
        return res.IsFaulted
            ? Fail(res.Exception)
            : Succ(res.Value);
    }
}

他们都允许以安全的方式调用Try委托。关键是Match扩展方法,其中&#39;模式匹配&#39;结果:

int res = CalculateSomething(1).Match(
              Succ: value => value,
              Fail: excep => 0 
              );

所以你被迫承认该函数可以抛出一个异常来获取该值。

这里缺少的一件事是它如何与LINQ一起使用:

public static class TryExtensions
{
    public static Try<U> Select<T, U>(this Try<T> self, Func<T, U> select) =>
        new Try<U>(() =>
        {
            var resT = self.Try();
            if (resT.IsFaulted) return new TryResult<U>(resT.Exception);
            return select(resT.Value);
        });

    public static Try<V> SelectMany<T, U, V>(
        this Try<T> self,
        Func<T, Try<U>> bind,
        Func<T, U, V> project ) =>
            new Try<V>(() =>
            {
                var resT = self.Try();
                if (resT.IsFaulted) return new TryResult<V>(resT.Exception);

                var resU = bind(resT.Value).Try();
                if (resU.IsFaulted) return new TryResult<V>(resT.Exception);

                return new TryResult<V>(project(resT.Value, resU.Value));
            });
}

Select允许此功能:

var res = from x in y
          select x;

SelectMany允许此功能:

var res = from x in y
          from z in x
          select z;

这就是它允许多个from语句按顺序运行。这被称为monadic bind(但你不需要知道为了这个工作 - 我不想在这里写一个monad教程)。基本上它是在CalculateSomething示例中捕获嵌套模式,因此您无需再次手动编写它。

就是这样。上面的代码,你只写一次和一次。现在让我们实现您的DoSomething功能:

public Try<int> DoSomethingA(int inputInt) => () =>
   inputInt;

public Try<int> DoSomethingB(int inputInt) => () =>
{
    throw new Exception();
};

注意他们如何被定义为lambdas。如果您使用C#5或更低版本,它们将如下所示:

public Try<int> DoSomethingA(int inputInt)
{
    return () => inputInt;
}

如果您希望更全面地了解此问题,请查看我的language-ext functional library for C#那里有Try implementation的地方。它有许多有用的扩展方法,允许您编写功能代码,而不需要使用try / catch的bolierplate。

答案 1 :(得分:0)

有时,遵循良好设计的代码可能比单片函数更难理解,但是重用代码的某些部分并避免代码重复更容易...

显然,通过正确命名对象和函数,代码应该更容易理解。

在上面的代码中,最好忽略函数1和2中的异常并在调用者处理它们。这样,行动就简化了。例如,以下代码更简单但等效:

public int? CalculateSomething(int input)
{
    try
    {
        var result1 = SomeMath(input);
        var result2 = SomeOtherMath(result1);
        return result2;
    }
    catch
    {
        return null;
    }
}

并且可以更容易地推广该代码以处理一个操作列表,其中一个函数的结果是下一个函数的输入。