-1

today I saw a piece of code and I am thinking about it's sense. So I made a little Example where you can see how it works. As far as I know it should be nested functionality with heavy use of Lambda-Caculus like it is used in functional Languages. Because I find that is not well understandable (Jumping from File to File) I only like to know if someone of you have similiar Experience with something like this.

Example Function One

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

Example Function Two

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

Example Call of both.

    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;
    }

It's maybe interesting that normale the "Main" Function would have some sort of promise. So it registered the Functions and waits for the end and get's then the result.

TheC
  • 15
  • 4
  • Obviously, your code does not compile... In function 1, you call `result` with `null` for the first parameter while it takes an `int`. In last code block, `result` is not always initialized. – Phil1970 Sep 07 '16 at 15:33
  • 4
    What's the actual question here? – Liam Sep 07 '16 at 16:01
  • Fact. Fixed it. Thank you! – TheC Sep 07 '16 at 16:01
  • A good functional implementation would have *separate* functions for success and failure. You wouldn't have to pass nulls to the success function this way, nor have to handle failures in the `result` function. Besides, what does `null` even mean? What failed? Is a file missing? Was an invalid value used? – Panagiotis Kanavos Sep 07 '16 at 16:13
  • Also note that Tasks allow you to pass a result to another function with `ContinueWith`. The `async/await` makes this much cleaner so you don't need callbacks – Panagiotis Kanavos Sep 07 '16 at 16:18
  • And finally, the TPL Dataflow library allows you to create a dataflow pipeline from functions *and* execute them in parallel – Panagiotis Kanavos Sep 07 '16 at 16:19

2 Answers2

1

What you're describing is a Monad. In particular the Try monad. I'll show below how to implement a Try monad, and your code for CalculateSomething will look like this:

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

Which by any measure is pretty easy to understand.

First declare a delegate that will represent the Try operation:

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

Next define TryResult<T>. It will capture the return value on-success, and the failure exception 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();
}

Next define some extension methods for the Try delegate (this is a little known feature of C# that delegates support extension methods):

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);
    }
}

They both allow invoking of the Try delegate in a safe way. The key is the Match extension method, which 'pattern matches' on the result:

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

So you're forced to acknowledge that the function could throw an exception to get at the value.

One thing that's missing here is how it works with 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 allows this to work:

var res = from x in y
          select x;

SelectMany allows this to work:

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

That is it allows multiple from statements to run in sequence. This is known as monadic bind (but you don't need to know that for this to work - an I'd rather not write a monad tutorial here). Essentially it is capturing the nesting pattern in your CalculateSomething example, so you never have to manually write it again.

And that's it. The code above, you write once and once only. Now let's implement your DoSomething functions:

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

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

Note how they're defined as lambdas. If you're using C# 5 or lower they would look like this:

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

If you want to see a fuller implementation of this, check out my language-ext functional library for C# where there's a Try implementation there. It has many more useful extension methods that allow you to write functional code without the bolierplate of try/catch.

louthster
  • 1,560
  • 9
  • 20
  • Awesome answer! Complex and Simple at the same time ... will use this for new Implementation of a current structure I think. – TheC Sep 09 '16 at 08:29
0

Well sometime code that follows good design can be harder to understand than a monolithic function but it is much easier to reuse some parts of the code and avoid code duplication...

Obviously, by properly naming objects and functions, code should be more understandable.

In the above code, it might be best to ignore exceptions in functions 1 and 2 and handle them at the caller. That way, actions are simplified. For example, the following code is much simpler yet equivalent:

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

And that code can be generalized more easily to handle a list of operations where the result of one function is the input of next function.

Phil1970
  • 2,605
  • 2
  • 14
  • 15