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