Search code examples
c#functional-programmingmonadsmethod-chaining

Retrieving Logging Messages From Wiki Monad Example Not Working


I am learning about monads. I read this Wiki page thoroughly and reached the Program logging section. I've decided to implement that in C#.

An example starts with two simple functions/methods, which just take an integer value as a function argument and return another integer value.

Then it describes a scenario where some logging needs to be performed inside these functions/methods:

But suppose we are debugging our program, and we would like to add logging messages to foo and bar. So we change the types as so:

    foo : int -> int * string
    bar : int -> int * string

So that both functions return a tuple, with the result of the application as the integer, and a logging message with information about the applied function and all the previously applied functions as the string.

Then Wiki explains the problem where with this method signature method chaining is no longer possible (because an input and output types are not the same).

Then, the example introduces a solution by adding a helper function/method called bind:

    bind : int * string -> (int -> int * string) -> int * string

What this one does is that it takes in two arguments:

  1. a tuple (of an integer and a log message string)
  2. a function that itself has a function argument (of type integer) and a return value of a tuple.

...and returns a tuple.

There is a really hard to follow explanation provided about this helper function/method, but it actually makes sense:

bind takes in an integer and string tuple, then takes in a function (like foo) that maps from an integer to an integer and string tuple. Its output is an integer and string tuple, which is the result of applying the input function to the integer within the input integer and string tuple. In this way, we only need to write boilerplate code to extract the integer from the tuple once, in bind.

And then it concludes:

Now we have regained some composability. For example: bind (bind (x,s) bar) foo

So, I've built all of this in C# app:

public static void Run()
{
    var resultOne = Bind(Bind(new(10, "Test"), BarWithLogging), FooWithLogging);
}

public static (int, string) FooWithLogging(int x)
{
    return (x, "Log from Foo");
}

public static (int, string) BarWithLogging(int x)
{
    return (x, "Log from Bar");
}

public static (int, string) Bind((int x, string s) tuple, Func<int, (int, string)> function)
{
    return function(tuple.x);
}

...and the problem is that my logging functionality just doesn't make sense. First of all, we have to pass a tuple to the bind function/method, and that tuple contains a log message; but I thought that log message should be created from the function/method foo and bar (in my case FooWithLogging and BarWithLogging) shouldn't it? If so, then does it mean we just have to pass a dummy log message tuple part to the bind function? It seems the answer is no because later, in that example there is another step that addresses the case when we'd like to not have to pass an empty log message when we want to create an empty log; which implies that if we want to log something that is not empty then we pass a non-empty message (and that non-empty message will be logged as a result).

...in that case how can we pass that log message value to the foo function? It accepts an integer only! And since it returns a tuple (that has the log message value) it leads me to a conclusion that the actual log message is generated within the foo function; but that contradicts my previous conclusion!

What am I missing? How to implement that monad concept so that I get a benefit of logging functionality?

Thanks in advance.


Solution

  • That Wikipedia article section looks a bit confusing. Normally, you'd use the State monad for something like that, but that part of the Wikipedia article instead uses what is essentially 'the' tuple functor, which is only a monad if the 'extra' data forms a monoid.

    Strings, fortunately, give rise to a monoid over concatenation, which is also why the Wikipedia article can get away with defining (x, "") as monadic return: The empty string ("") is the identity for the string concatenation monoid.

    This also means that in order to be able to carry both the existing and the new 'log' string through the bind method, you'll need to use the monoidal binary operation. For string concatenation, that is exactly that: Concatenation.

    So the bind function should look like this:

    public static (int, string) Bind((int x, string s) tuple, Func<int, (int, string)> function)
    {
        var newTuple = function(tuple.x);
        return (newTuple.x, tuple.Snd + newTuple.Snd);
    }
    

    I haven't tried to compile this, so it's possible that there's a typo, but I hope it gets the point across.

    Another option is to concatenate in the opposite order: newTuple.Snd + tuple.Snd.

    Since it looks like you are working with C# you may find my articles series on monads useful. I'll get to the State monad in a couple of weeks, but already today you can take a sneak peek at the State functor.