Language-ext: how to "Tee" (similar to "Do") for side effects

Created on 27 Jun 2019  路  7Comments  路  Source: louthy/language-ext

What is the recommended approach for doing a "tee" in LanguageExt? The concept is similar to Option.Do but would work whether the option is Some or None. (A tee pipe fitting lets fluid flow through but also has a different direction... the side effect.)

My common use case is to log something before a function returns. I've done this by making a Tee object extension that runs an action and returns the same object. Example usage:

return maybe
   .Tee(_ => _logger.LogDebug("some message"));

Is this concept addressed already in LanguageExt? What would be a better approach?

Thank you.

examples / documentation

Most helpful comment

I'm curious how you two typically handle logging in your sw dev..

@jltrem If I have an suitably complex sub-system then I usually build a bespoke monad that captures the rules of that sub-system. Whether it's state, logging, environment (config, etc.), or control flow. My code then becomes a set of sub-systems, some that wrap others. A good example might be a compiler, where I will have sub-system monads for parsing, type-inference, code-gen, etc. that would be wrapped by a compiler monad that works with them all.

A sub-system monad _shape_ would be defined like so:
```c#
public delegate Out Subsystem();

I've picked the name `Subsystem` as an example, it could be `Tokeniser<A>`, or `TypeInfer<A>`, for example.

This monad takes no input and returns an `Out<A>`.  The `Out<A>` will wrap an `A` (the bound value of the monad) and various other stuff I want to capture.  So, in your case you could wrap up a `Seq<string>`  for a list of output log entries.  I usually wrap up an `Error` type as well:

```c#
    public struct Error
    {
        public readonly string Message;
        public readonly Option<Exception> Exception;

        Error(string message, Exception exception)
        {
            Message = message;
            Exception = exception;
        }

        public static Error FromString(string message) =>
            new Error(message, null);

        public static Error FromException(Exception ex) =>
            new Error(ex.Message, ex);
    }

And so the Out<A> can now be defined:

```c#
public struct Out

{
public readonly A Value;
public readonly Seq Output;
public readonly Option Error;

    Out(A value, Seq<string> output, Option<Error> error)
    {
        Value = value;
        Output = output;
        Error = error;
    }

    public static Out<A> FromValue(A value) =>
        new Out<A>(value, Empty, None);

    public static Out<A> FromValue(A value, Seq<string> output) =>
        new Out<A>(value, output, None);

    public static Out<A> FromError(Error error) =>
        new Out<A>(default, Empty, error);

    public static Out<A> FromError(Error error, Seq<string> output) =>
        new Out<A>(default, output, error);

    public static Out<A> FromError(string message) =>
        new Out<A>(default, Empty, SubsystemTest.Error.FromString(message));

    public static Out<A> FromException(Exception ex) =>
        new Out<A>(default, Empty, SubsystemTest.Error.FromException(ex));

    public bool HasFailed => Error.IsSome;
The `static` methods on both types are just there for friendly construction.

I will then define a `Subsystem` static class that wraps up the behaviour of the monad.  So, first I'll define the success and fail methods:
```c#
    public static class Subsystem
    {
        public static Subsystem<A> Return<A>(A value) => () =>
            Out<A>.FromValue(value);

        public static Subsystem<A> Fail<A>(Exception exception) => () =>
            Out<A>.FromException(exception);

        public static Subsystem<A> Fail<A>(string message) => () =>
            Out<A>.FromError(message);
    }

Notice how they're all lambdas. The pattern matches the Subystem<A> delegate and so they implicitly convert to the return type.

Then I'll add the Bind function for the monad to the Subsystem static class:
```c#
public static Subsystem Bind(this Subsystem
ma, Func> f) => () =>
{
try
{
// Run ma
var outA = ma();

        if(outA.HasFailed)
        {
            // If running ma failed then early out
            return Out<B>.FromError((Error)outA.Error, outA.Output);
        }
        else
        {
            // Run the bind function to get the mb monad
            var mb = f(outA.Value);

            // Run the mb monad
            var outB = mb();

            // Concatenate the output from running ma and mb
            var output = outA.Output + outB.Output;

            // Return our result
            return outB.HasFailed
                ? Out<B>.FromError((Error)outB.Error, output)
                : Out<B>.FromValue(outB.Value, output);
        }
    }
    catch (Exception e)
    {
        // Capture exceptions
        return Out<B>.FromException(e);
    }
};
The bind function is where you insert all the _magic_ for your bespoke monad.  This essentially runs _between the lines_ of all operations and can do special stuff.  So in this case it does error handling early-outs (like `Option`, and `Either`) and exception capture (like `Try`) as well as log collection (like `Writer`).

Once you have the `Bind` function then the rest of the stuff that makes the type into a functor and makes it work with LINQ is almost free:
```c#
        public static Subsystem<B> Map<A, B>(
            this Subsystem<A> ma, 
            Func<A, B> f) =>
                ma.Bind(a => Return(f(a)));

        public static Subsystem<B> Select<A, B>(
            this Subsystem<A> ma, 
            Func<A, B> f) =>
                ma.Bind(a => Return(f(a)));

        public static Subsystem<B> SelectMany<A, B>(
            this Subsystem<A> ma, 
            Func<A, Subsystem<B>> f) =>
                ma.Bind(f);

        public static Subsystem<C> SelectMany<A, B, C>(
            this Subsystem<A> ma, 
            Func<A, Subsystem<B>> bind, 
            Func<A, B, C> project) =>
                ma.Bind(a => bind(a).Map(b => project(a, b)));

You'll notice everything is written in terms of Bind - it's usually trivial to do this once you have Bind defined.

Finally we want to add a Log function to Subsystem:
```c#
public static Subsystem Log(string message) => () =>
Out.FromValue(unit, Seq1(message));

Note how it just creates a single item `Seq<string>`.  It doesn't need to care about how the log is built, it just needs to return a single value sequence and then the bind function does the work of joining it with other logs.

We could also add a little helper function to the `Out<A>` type to make debugging a touch easier:
```c#
        public Unit Show()
        {
            var self = this;
            Error.Match(
                Some: err => Console.WriteLine($"Error is: {err.Message}"),
                None: ()  => Console.WriteLine($"Result is: {self.Value}"));

            Console.WriteLine();
            Console.WriteLine("Output");
            Console.WriteLine();

            foreach(var log in Output)
            {
                Console.WriteLine(log);
            }

            return unit;
        }

So, now the sub-system monad is defined, we can use it. Below are two functions defined that both do some logging. The MakeValue function logs the value provided as well as returning it. The Add function adds two values together.
```c#
public static Subsystem
MakeValue(A value) =>
from _ in Subsystem.Log($"Making value {value}")
select value;

    public static Subsystem<int> Add(int x, int y) =>
        from a in MakeValue(x)
        from b in MakeValue(y)
        from r in Subsystem.Return(a + b)
        from _ in Subsystem.Log($"{a} + {b} = {r}")
        select r;
Not a particularly spectacular demo I know, but it should give you an idea:
```c#
        static void Main(string[] args)
        {
            // Build expression
            var expr = Add(10, 20);

            // Run expression
            var result = expr();

            // Show results and output log
            result.Show();
        }

The output is:

Result is: 30

Output

Making value 10
Making value 20
10 + 20 = 30

If you start working this way you'll realise you can wrap up a lot of the scaffolding of common code patterns inside the bind function of any bespoke monad you decide to build. It also means if you decide later to add features to your monad that all existing code gets it by default (without having to thread through context objects, or use dependency injection or any of that nonsense).

Finally, by including using static SubsystemTest.Subsystem it's possible to make the LINQ a bit more elegant:
```c#
public static Subsystem
MakeValue(A value) =>
from _ in Log($"Making value {value}")
select value;

    public static Subsystem<int> Add(int x, int y) =>
        from a in MakeValue(x)
        from b in MakeValue(y)
        from r in Return(a + b)
        from _ in Log($"{a} + {b} = {r}")
        select r;

```

All 7 comments

In FP, when you want logging (or telemetry, or any other kind of indirect output), you should think of the Writer monad. Here are some examples:
https://github.com/louthy/language-ext/blob/509baee899023883e99c296f497293c60d368df0/Samples/TestBed/Program.cs#L169
https://github.com/louthy/language-ext/blob/c412a962fac7110845b80d0f80a467cc929637ff/LanguageExt.Tests/MonadTests.cs#L15
The only difference from what you were asking for is that Writer doesn't perform a side effect; instead it accumulates what you tell in a monoid(commonly a Seq), which keeps your operation pure. And you use that pure operation as part of a impure-pure-impure sandwich, where the final impure operation takes the .Output of the writer result and sends it to the logger.

@gwintering thanks for the reply. This doesn't immediately "click" and may take me some time to comprehend. If you have any articles that address this in more depth please add them here. I want to get a solid understanding of how this works in general for FP; viz. how an idiomatic solution looks in F# and Haskel.

@jltrem There isn't anything built into lang-ext for that. You could build one yourself relatively easily:
c# public static class ObjExt { public static A Tee<A>(this A self, Func<A, Unit> f) { f(self); return self; } public static A Tee<A>(this A self, Action<A> f) { f(self); return self; } }

Similar issue: #228

Super @gwintering -- thanks for finding that discussion. @louthy that Tee extension is exactly what I've done. My concern was the same as you mentioned in #228: "I've kind of resisted it because it goes against the core concepts of functional programming (where we should avoid side-effects)."

I'm curious how you two typically handle logging in your sw dev... using a Do/Tee approach? Monadic writer? Plain old imperative? If this isn't the appropriate place to discuss, feel free to ping me on Twitter with the same handle: @jltrem.

I'm curious how you two typically handle logging in your sw dev..

@jltrem If I have an suitably complex sub-system then I usually build a bespoke monad that captures the rules of that sub-system. Whether it's state, logging, environment (config, etc.), or control flow. My code then becomes a set of sub-systems, some that wrap others. A good example might be a compiler, where I will have sub-system monads for parsing, type-inference, code-gen, etc. that would be wrapped by a compiler monad that works with them all.

A sub-system monad _shape_ would be defined like so:
```c#
public delegate Out Subsystem();

I've picked the name `Subsystem` as an example, it could be `Tokeniser<A>`, or `TypeInfer<A>`, for example.

This monad takes no input and returns an `Out<A>`.  The `Out<A>` will wrap an `A` (the bound value of the monad) and various other stuff I want to capture.  So, in your case you could wrap up a `Seq<string>`  for a list of output log entries.  I usually wrap up an `Error` type as well:

```c#
    public struct Error
    {
        public readonly string Message;
        public readonly Option<Exception> Exception;

        Error(string message, Exception exception)
        {
            Message = message;
            Exception = exception;
        }

        public static Error FromString(string message) =>
            new Error(message, null);

        public static Error FromException(Exception ex) =>
            new Error(ex.Message, ex);
    }

And so the Out<A> can now be defined:

```c#
public struct Out

{
public readonly A Value;
public readonly Seq Output;
public readonly Option Error;

    Out(A value, Seq<string> output, Option<Error> error)
    {
        Value = value;
        Output = output;
        Error = error;
    }

    public static Out<A> FromValue(A value) =>
        new Out<A>(value, Empty, None);

    public static Out<A> FromValue(A value, Seq<string> output) =>
        new Out<A>(value, output, None);

    public static Out<A> FromError(Error error) =>
        new Out<A>(default, Empty, error);

    public static Out<A> FromError(Error error, Seq<string> output) =>
        new Out<A>(default, output, error);

    public static Out<A> FromError(string message) =>
        new Out<A>(default, Empty, SubsystemTest.Error.FromString(message));

    public static Out<A> FromException(Exception ex) =>
        new Out<A>(default, Empty, SubsystemTest.Error.FromException(ex));

    public bool HasFailed => Error.IsSome;
The `static` methods on both types are just there for friendly construction.

I will then define a `Subsystem` static class that wraps up the behaviour of the monad.  So, first I'll define the success and fail methods:
```c#
    public static class Subsystem
    {
        public static Subsystem<A> Return<A>(A value) => () =>
            Out<A>.FromValue(value);

        public static Subsystem<A> Fail<A>(Exception exception) => () =>
            Out<A>.FromException(exception);

        public static Subsystem<A> Fail<A>(string message) => () =>
            Out<A>.FromError(message);
    }

Notice how they're all lambdas. The pattern matches the Subystem<A> delegate and so they implicitly convert to the return type.

Then I'll add the Bind function for the monad to the Subsystem static class:
```c#
public static Subsystem Bind(this Subsystem
ma, Func> f) => () =>
{
try
{
// Run ma
var outA = ma();

        if(outA.HasFailed)
        {
            // If running ma failed then early out
            return Out<B>.FromError((Error)outA.Error, outA.Output);
        }
        else
        {
            // Run the bind function to get the mb monad
            var mb = f(outA.Value);

            // Run the mb monad
            var outB = mb();

            // Concatenate the output from running ma and mb
            var output = outA.Output + outB.Output;

            // Return our result
            return outB.HasFailed
                ? Out<B>.FromError((Error)outB.Error, output)
                : Out<B>.FromValue(outB.Value, output);
        }
    }
    catch (Exception e)
    {
        // Capture exceptions
        return Out<B>.FromException(e);
    }
};
The bind function is where you insert all the _magic_ for your bespoke monad.  This essentially runs _between the lines_ of all operations and can do special stuff.  So in this case it does error handling early-outs (like `Option`, and `Either`) and exception capture (like `Try`) as well as log collection (like `Writer`).

Once you have the `Bind` function then the rest of the stuff that makes the type into a functor and makes it work with LINQ is almost free:
```c#
        public static Subsystem<B> Map<A, B>(
            this Subsystem<A> ma, 
            Func<A, B> f) =>
                ma.Bind(a => Return(f(a)));

        public static Subsystem<B> Select<A, B>(
            this Subsystem<A> ma, 
            Func<A, B> f) =>
                ma.Bind(a => Return(f(a)));

        public static Subsystem<B> SelectMany<A, B>(
            this Subsystem<A> ma, 
            Func<A, Subsystem<B>> f) =>
                ma.Bind(f);

        public static Subsystem<C> SelectMany<A, B, C>(
            this Subsystem<A> ma, 
            Func<A, Subsystem<B>> bind, 
            Func<A, B, C> project) =>
                ma.Bind(a => bind(a).Map(b => project(a, b)));

You'll notice everything is written in terms of Bind - it's usually trivial to do this once you have Bind defined.

Finally we want to add a Log function to Subsystem:
```c#
public static Subsystem Log(string message) => () =>
Out.FromValue(unit, Seq1(message));

Note how it just creates a single item `Seq<string>`.  It doesn't need to care about how the log is built, it just needs to return a single value sequence and then the bind function does the work of joining it with other logs.

We could also add a little helper function to the `Out<A>` type to make debugging a touch easier:
```c#
        public Unit Show()
        {
            var self = this;
            Error.Match(
                Some: err => Console.WriteLine($"Error is: {err.Message}"),
                None: ()  => Console.WriteLine($"Result is: {self.Value}"));

            Console.WriteLine();
            Console.WriteLine("Output");
            Console.WriteLine();

            foreach(var log in Output)
            {
                Console.WriteLine(log);
            }

            return unit;
        }

So, now the sub-system monad is defined, we can use it. Below are two functions defined that both do some logging. The MakeValue function logs the value provided as well as returning it. The Add function adds two values together.
```c#
public static Subsystem
MakeValue(A value) =>
from _ in Subsystem.Log($"Making value {value}")
select value;

    public static Subsystem<int> Add(int x, int y) =>
        from a in MakeValue(x)
        from b in MakeValue(y)
        from r in Subsystem.Return(a + b)
        from _ in Subsystem.Log($"{a} + {b} = {r}")
        select r;
Not a particularly spectacular demo I know, but it should give you an idea:
```c#
        static void Main(string[] args)
        {
            // Build expression
            var expr = Add(10, 20);

            // Run expression
            var result = expr();

            // Show results and output log
            result.Show();
        }

The output is:

Result is: 30

Output

Making value 10
Making value 20
10 + 20 = 30

If you start working this way you'll realise you can wrap up a lot of the scaffolding of common code patterns inside the bind function of any bespoke monad you decide to build. It also means if you decide later to add features to your monad that all existing code gets it by default (without having to thread through context objects, or use dependency injection or any of that nonsense).

Finally, by including using static SubsystemTest.Subsystem it's possible to make the LINQ a bit more elegant:
```c#
public static Subsystem
MakeValue(A value) =>
from _ in Log($"Making value {value}")
select value;

    public static Subsystem<int> Add(int x, int y) =>
        from a in MakeValue(x)
        from b in MakeValue(y)
        from r in Return(a + b)
        from _ in Log($"{a} + {b} = {r}")
        select r;

```

@louthy this example is enlightening. Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

michael-wolfenden picture michael-wolfenden  路  5Comments

chyczewski-maciej picture chyczewski-maciej  路  4Comments

Lonli-Lokli picture Lonli-Lokli  路  5Comments

martasp picture martasp  路  3Comments

StefanBertels picture StefanBertels  路  3Comments