la-yumba / functional-csharp-code

Code samples for Functional Programming in C#

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Example using Exceptional.Apply

diegoparraleal opened this issue · comments

Do you have a working example of using Exceptional.Apply?

I'm a little confused in the usage, I'm supposed to Lift first a fn to Exceptional and then apply the arguments

In the following snippet, the first line works and the second get an unmanaged exception. Not sure if apply should manage that in the same way Try.Run does, any thoughts?

   [Test]
    public void Test_Apply()
    {
        // Arrange
        Func<string, int> ParseString = text => int.Parse(text);
        var SafeParseString = Exceptional(ParseString);
        
        // Act
        var result = SafeParseString.Apply(Exceptional("1"));
        var result2 = SafeParseString.Apply(Exceptional("X"));
    }

This code allows me to manage the exception properly:


public static Exceptional<R> ApplyX<T, R>(this Exceptional<Func<T, R>> @this, Exceptional<T> arg)
        => @this.Match
        (
            Exceptional.Of<R>,
            fn => arg.Match(
                ex => ex,
                t => TryRun(() => fn(t)) 
            )   
        );

Why original extension does not manage the possible failure?

You know, if I lift a function that manages itself the error, it should be Func<T, Exceptional> and at the end there will be an additional wrapping in the response, something like Exceptional<Exceptional>

Hi @diegoparraleal ,

thanks for raising this issue. Although I see your point, the problem is that you're starting out with a function that is unsafe, ParseString. There is no reason for Apply or Return (in this case called Exceptional) to do any clever checks: if you start with something that is unsafe, then it will be unsafe even if you run it in the context of Map, Apply or other HOFs.

Instead, either make your function "honest" by returning Exceptional<int>, or use Try.Run to do that for you (this would be the more elegant option in this scenario), as shown in the section of the book called "Exception handling with Try".

I hope this helps to clarify.

Just so you know, there is a new edition of the book out, and the corresponding codebase is at https://github.com/la-yumba/functional-csharp-code-2

Hi Enrico, Thanks for your quick reply, I tried first to use cleanly the Apply (the one at your implementation), but it wraps the result inside a Success which is very confusing

    [Test]
    public void Test_Apply()
    {
        // Arrange
        Func<string, Exceptional<int>> SafeParseString = text => TryRun(() => int.Parse(text));
        
        // Act
        var result = Exceptional(SafeParseString).Apply(Exceptional("1")); // Success(Success(1))
        var result2 = Exceptional(SafeParseString).Apply(Exceptional("X")); // Success(Exception(...))
    }

I think the problem is that you're trying to use Apply in a scenario in which it may not be the best solution. Your scenario would best be addressed as follows:

Try<int> ParseInt(string s) => () => int.Parse(s);
ParseInt("1").Run() // Success(1)
ParseInt("X").Run() // Exception(...)

Although Apply can be used with functions of any arity, it's very contrived to use it with unary functions. It's a better fit for cases in which you want to combine the result from several computations that return an Exceptional, for example:

// computes an A, wrapped in an Exceptional
Exceptional<A> CalcA(string s) => //...

// computes a B, wrapped in an Exceptional
Exceptional<B> CalcA(string s) => //...

// consumes an A and a B to compute a C
C CalcC(A a, B b) => //...

// compute CalcC even though its args are wraped in Exceptionals
Exceptional<C> c = Exceptional(CalcC).Apply(CalcA("A")).Apply(CalcB("B"))

(note: you may have to write CalcC as a Func for the above to compile)

Now, to be fair, my implementation of Apply could be improved to collect the exceptions caught in CalcA and CalcB into an AggregateException (like Apply does in the implementation of Validation). Otherwise, there's really no point in using Apply with exceptional, you'd always use the monadic version, which is the much more readable:

Exceptional<C> c = 
   from a in CalcA("A") 
   from b in CalcB("B")
   select CalcC(a, b);