louthy / language-ext

C# pure functional programming framework - come and get declarative!
MIT License
6.53k stars 422 forks source link

Add IfSuccAsync and IfFailAsync methods to the Result<A> type #1371

Closed pawelFelcyn closed 3 weeks ago

pawelFelcyn commented 1 month ago

Currently, If you want to perform a conditional action depending on the result's state being successful or not, you can go for something like this:

var result = GetSomeResult();

result.IfSucc(res =>
{
    //here you can perform some action
});

result.IfFail(ex =>
{
    //here you can perform some action
});

It would be great, if also an async version of it was supported, like this:

var result = GetSomeResult();

await result.IfSuccAsync(async res =>
{
    //here you can perform some action
});

await result.IfFailAsync(async ex =>
{
    //here you can perform some action
});
louthy commented 1 month ago

There are some issues here: Result<A> is not a supported type and should not be used directly: as mentioned in the documentation; Result<A> is was purely used as an intermediate type for the Try* types and wasn't for general consumption.

Because of this endless confusion (you're not the first to ask!) about the purpose of Result<A>, it has been removed from v5 of language-ext. So, the best advice I can give right now is to stop using Result<A>.

The type you want to use is Fin<A> (French for 'end'/'finished' ... i.e. result).

But even Fin<A> doesn't have IfSuccAsync and IfFailAsync. Why? Because the Fin<A> monad has one effect: an alternative-value of Error. It doesn't support IO (Task is always IO), because it's not an IO monad. Types like EitherAsync and OptionAsync do support IO. Unfortunately (in v4) there is no FinAsync. So, if you stay on v4 then you'll need to add any extensions yourself.

In v5 there is a move to put IO in its correct box and make it fully declarative and compositional.

The first step of that is to create an IO<A> monad. It is the foundational monad for all IO effects. It encapsulates both synchronous, asynchronous, and parallel effects.

The next step of that is to drop all *Async variants from the library. So, OptionAsync, EitherAsync, TryAsync, and TryOptionAsync are now gone.

The final step is to add monad-transformers to the library. Including:

These transformers can all be 'stacked' to combine behaviours. So, if you want the equivalent of OptionAsync<A>, you stack the OptionT transformer with the IO monad: OptionT<IO, A>.

That gives the same behaviour, but this works for all combinations of transformer and monad. So, in your case, where you're mixing IO side-effects with the 'alternative value of Error' effect, you could use FinT<IO, A> to support the same behaviour.

If you have a big code-base using language-ext then I wouldn't recommend moving over the v5 as it's still in beta and the changes are substantial. If you're relatively new to language-ext then you might want to try out v5 to start leveraging these new capabilities.

Otherwise, I'm afraid that if you want the extensions IfSuccAsync and IfFailAsync, then you'll need to add them yourself. Or, you'll need to implement your own FinAsync<A> type, because unfortunately that's not the direction of travel for the library.