DigitecGalaxus / Galaxus.Functional

A package bringing popular functional abstractions (e.g. Option or Result) to C#.
Other
37 stars 12 forks source link

Introduce Tap #21

Open raphaelimahorn opened 3 years ago

raphaelimahorn commented 3 years ago

When chaining Result's, there is often the need to work with the results value more than once, e.g. for logging and inform some listeners. One can use the functions Map and AndThen to do that, however, we than have to return the input again, which often requires to use heavier code, while with a Tap often a simple method group could be used.

With Tap one could write

result
    .Tap(Console.Write)
    .Tap(Publish);

Without the same functionality looks something like this:

result.
    .Map(ok =>
   {
        Console.Write(ok);
        return ok;
   }).Map(Publish);

The proposed naming is inspired by tap from rxjs and can be argued because of it's simmilarity to map.

As for Map and AndThen also a TapAsync as well as TapErr and TapErrAsync should be introduced.

phkiener commented 3 years ago

Instead of introducing a whoe batch of new methods (Tap, TapAsync, TapErr, TapErrAsync, ...), I strongly believe in function composition. The side-effects like logging are often actions as in returning void instead of a value - which is impossible to compose. Depending on your exact code, you should easily get away with a private helper to handle the "execute the action and return the given value". If that's not the case, a more generic Do higher-order function can work for you.. something like

public static T Do<T>(Action<T> action)
{
    return x => {
        action.Invoke(x);
        return x;
    };
}

I still am unsure whether we'd want stuff like this in Galaxus.Functional - there're many similar nice helpers, but they don't flow that well in actual production code I think.

SamuelBittmann commented 3 years ago

While we didn't call it Tap(...) (we followed the naming suggestion from Scott Wlaschin), we used to have a very similar extension method in our code to handle this. Since then the code calling the extension method changed and we no longer need this extension anymore, but as Scott states in his talk, Railway oriented programming (https://fsharpforfunandprofit.com/rop/), handing over a value to a method returning void is a quite common need. How about we add Tap(...), Tee(...), or whatever we want to call it, directly to the types and move all separate concerns (e.g. async) to their own project, as suggested here: https://github.com/DigitecGalaxus/Galaxus.Functional/issues/25?