Category Theory via C# (21) More Monad: Writer< , > Monad

[LINQ via C# series]

[Category Theory via C# series]

Latest version: https://weblogs.asp.net/dixin/category-theory-via-csharp-8-more-linq-to-monads

Writer< , > monad

Unlike the Reader< , > monad, the Writer< , > monad output contents with a sequence of functions:

public class Writer<T, TContent>
{
    private readonly Lazy<Tuple<T, TContent>> lazy;

    public Writer(Func<Tuple<T, TContent>> factory, IMonoid<TContent> monoid)
    {
        this.lazy = new Lazy<Tuple<T, TContent>>(factory);
        this.Monoid = monoid;
    }

    public T Value
    {
        [Pure]get { return this.lazy.Value.Item1; }
    }


    public TContent Content
    {
        [Pure]get { return this.lazy.Value.Item2; }
    }

    public IMonoid<TContent> Monoid {[Pure] get; }
}

A Writer< , > is more complex than Reader< , >. It is a pair of value and output content, plus a monoid. A monoid is needed because its Binary operator is used to combine multiple output contents into one.

This is the SelectMany:

[Pure]
public static partial class WriterExtensions
{
    // Required by LINQ.
    public static Writer<TResult, TContent> SelectMany<TSource, TContent, TSelector, TResult>
        (this Writer<TSource, TContent> source,
         Func<TSource, Writer<TSelector, TContent>> selector,
         Func<TSource, TSelector, TResult> resultSelector) => 
            new Writer<TResult, TContent>(() =>
                {
                    Writer<TSelector, TContent> selectorResult = selector(source.Value);
                    return Tuple.Create(
                        resultSelector(source.Value, selectorResult.Value),
                        source.Monoid.Binary(source.Content, selectorResult.Content));
                }, source.Monoid);

    // Not required, just for convenience.
    public static Writer<TResult, TContent> SelectMany<TSource, TContent, TResult>
        (this Writer<TSource, TContent> source,
            Func<TSource, Writer<TResult, TContent>> selector) => source.SelectMany(selector, Functions.False);
}

so that

// [Pure]
public static partial class WriterExtensions
{
    // μ: Writer<Writer<T, TContent>> => Writer<T, TContent>
    public static Writer<TResult, TContent> Flatten<TResult, TContent>
        (Writer<Writer<TResult, TContent>, TContent> source) => source.SelectMany(Functions.Id);

    // η: T -> Writer<T, TContent>
    public static Writer<T, TContent> Writer<T, TContent>
        (this T value, TContent content, IMonoid<TContent> monoid) => 
            new Writer<T, TContent>(() => Tuple.Create(value, content), monoid);

    // φ: Lazy<Writer<T1, TContent>, Writer<T2, TContent>> => Writer<Lazy<T1, T2>, TContent>
    public static Writer<Lazy<T1, T2>, TContent> Binary<T1, T2, TContent>
        (this Lazy<Writer<T1, TContent>, Writer<T2, TContent>> binaryFunctor) => 
            binaryFunctor.Value1.SelectMany(
                value1 => binaryFunctor.Value2,
                (value1, value2) => new Lazy<T1, T2>(value1, value2));

    // ι: TUnit -> Writer<TUnit, TContent>
    public static Writer<Unit, TContent> Unit<TContent>
        (Unit unit, TContent content, IMonoid<TContent> monoid) => unit.Writer(content, monoid);

    // Select: (TSource -> TResult) -> (Writer<TSource, TContent> -> Writer<TResult, TContent>)
    public static Writer<TResult, TContent> Select<TSource, TResult, TContent>
        (this Writer<TSource, TContent> source, Func<TSource, TResult> selector) => 
            source.SelectMany(value => selector(value).Writer(source.Content, source.Monoid));
}

A typical usage is to output string logs when applying a sequence of functions:

// [Pure]
public static partial class WriterExtensions
{
    public static Writer<TSource, IEnumerable<string>> WithLog<TSource>(this TSource value, string log) =>
        value.Writer(
            $"{DateTime.Now.ToString("o", CultureInfo.InvariantCulture)} - {log}".Enumerable(),
            Enumerable.Empty<string>().Monoid((a, b) => a.Concat(b)));
}

Take previous IEnumerable stack as example:

public static void Stack()
{
    IEnumerable<int> stack = Enumerable.Empty<int>();
    Writer<IEnumerable<int>, IEnumerable<string>> writer =
        from lazy1 in stack.Push(1).WithLog("Push 1 to stack.")
        from lazy2 in lazy1.Value2.Push(2).WithLog("Push 2 to stack.")
        from lazy3 in lazy2.Value2.Pop().WithLog("Pop 2 from stack.")
        from stack1 in Enumerable.Range(0, 3).WithLog("Reset stack to 0, 1, 2.")
        from lazy4 in stack1.Push(4).WithLog("Push 4 to stack.")
        from lazy5 in lazy4.Value2.Pop().WithLog("Pop 4 from stack.")
        from stack2 in lazy5.Value2.WithLog("Get current stack.")
        select stack2;

    IEnumerable<int> resultStack = writer.Value;
    IEnumerable<string> logs = writer.Content;
    logs.ForEach(log => Trace.WriteLine(log));
}

The logs will be like:

2015-05-25T10:18:50.1769264-07:00 - Push 1 to stack.
2015-05-25T10:18:50.1769264-07:00 - Push 2 to stack.
2015-05-25T10:18:50.2082128-07:00 - Pop 2 from stack.
2015-05-25T10:18:50.2082128-07:00 - Reset stack to 0, 1, 2.
2015-05-25T10:18:50.2082128-07:00 - Push 4 to stack.
2015-05-25T10:18:50.2082128-07:00 - Pop 4 from stack.
2015-05-25T10:18:50.2082128-07:00 - Get current stack.

Monad laws, and unit tests

public partial class MonadTests
{
    [TestMethod()]
    public void WriterTest()
    {
        bool isExecuted1 = false;
        Func<int> f1 = () => 1;
        Func<int, Func<int, Func<string, int>>> f2 = x => y => z =>
            { isExecuted1 = true; return x + y + z.Length; };
        Writer<int, IEnumerable<string>> query = from x in f1().WithLog("a")
                                                    from y in 2.WithLog("b")
                                                    from z in "xyz".WithLog("c")
                                                    select f2(x)(y)(z);
        Assert.IsFalse(isExecuted1); // Laziness.
        Assert.AreEqual(1 + 2 + "xyz".Length, query.Value); // Execution.
        string[] logs = query.Content.ToArray();
        Assert.IsTrue(logs.Length==3);
        Assert.IsTrue(logs[0].EndsWith(" - a"));
        Assert.IsTrue(logs[1].EndsWith(" - b"));
        Assert.IsTrue(logs[2].EndsWith(" - c"));
        Assert.IsTrue(isExecuted1);

        IMonoid<string> monoid = string.Empty.Monoid((a, b) => string.Concat(a, b));
        // Monad law 1: m.Monad().SelectMany(f) == f(m)
        Func<int, Writer<int, string>> addOne = x => (x + 1).Writer("a", monoid);
        Writer<int, string> left = 1.Writer("b", monoid).SelectMany(addOne);
        Writer<int, string> right = addOne(1);
        Assert.AreEqual(left.Value, right.Value);
        Assert.AreEqual("ba", left.Content);
        Assert.AreEqual("a", right.Content);
        // Monad law 2: M.SelectMany(Monad) == M
        Func<int, Writer<int, string>> Resturn = x => x.Writer("abc", monoid);
        Writer<int, string> M = Resturn(1);
        left = M.SelectMany(Resturn);
        right = M;
        Assert.AreEqual(left.Value, right.Value);
        Assert.AreEqual("abcabc", left.Content);
        Assert.AreEqual("abc", right.Content);
        // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2))
        Func<int, Writer<int, string>> addTwo = x => (x + 2).Writer("b", monoid);
        left = M.SelectMany(addOne).SelectMany(addTwo);
        right = M.SelectMany(x => addOne(x).SelectMany(addTwo));
        Assert.AreEqual(left.Value, right.Value);
        Assert.AreEqual("abcab", left.Content);
        Assert.AreEqual("abcab", right.Content);
    }
}

20 Comments

  • https://ma-study.blogspot.com/

  • Based on the general information included in my dissertation, residencylor service created a stunning sum-up in an introductory chapter. A good thing to be given is a scheduled regular update from their writer who was making sure I get all I need and when I need it. Thanks a lot for the job you do. I`m grateful for being their client.

  • Wow, fantastic blog layout! How long have
    you been blogging for? you made blogging look easy.
    The overall look of your website is excellent, as well as the content!

  • Wonderful article! We will be linking to this great article
    on our site. Keep up the good writing.

  • Every weekend i used to visit this web page, for the reason that i want enjoyment, as this this
    web site conations truly pleasant funny information too.

  • Wonderful beat ! I would like to apprentice at the same
    time as you amend your web site, how can i subscribe for a weblog web site?
    The account aided me a applicable deal. I were a
    little bit acquainted of this your broadcast provided vivid clear concept

  • I used to visit this website every weekend because I wanted to have fun and since it also had genuinely amusing and humorous content.

  • From some point on, I am preparing to build my site while browsing various sites. It is now somewhat completed. If you are interested, please come to play with Keo nha cai !!

  • I'm writing on this topic these days, but I have stopped writing because there is no reference material. Then I accidentally found your article. I can refer to a variety of materials, so I think the work I was preparing will work! Thank you for your efforts.

  • Wordle is a word puzzle game that was created by Jonathan Feinberg. The game is simple - each round, you are given six chances to guess a five-letter word. After each guess, the game tells you which letters are correct and in the right position, which letters are correct but in the wrong position, and which letters are not in the word at all. Using this information, you must guess the word before your six chances run out.

  • worlde(Worlde) is a word puzzle game and a text visualization tool that was created by Jonathan Feinberg in 2008. It generates “word clouds” from a block of text by displaying the most frequently used words in a unique, eye-catching layout. The size of each word in the cloud represents its frequency in the text, with the most frequently used words appearing in larger font sizes.

  • I have been looking for articles on these topics for a long time. <a href="https://google.lk/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">safetoto</a> I don't know how grateful you are for posting on this topic. Thank you for the numerous articles on this site, I will subscribe to those links in my bookmarks and visit them often. Have a nice day

  • I've been searching for hours on this topic and finally found your post. <a href="https://google.li/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casino online</a>, I have read your post and I am very impressed. We prefer your opinion and will visit this site frequently to refer to your opinion. When would you like to visit my site?

  • First of all, thank you for your post. <a href="https://google.la/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">totosite</a> Your posts are neatly organized with the information I want, so there are plenty of resources to reference. I bookmark this site and will find your posts frequently in the future. Thanks again ^^

  • I'm writing on this topic these days, <a href="https://google.kz/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccarat online</a>, but I have stopped writing because there is no reference material. Then I accidentally found your article. I can refer to a variety of materials, so I think the work I was preparing will work! Thank you for your efforts.

  • Your article is extremely attractive and interesting, hopefully more people will know and visit your blog.

  • Hard to ignore such an amazing article like this. You really amazed me with your writing talent. Thank for you shared again.

  • I am very pleased to read this article.

  • Your article is very interesting. I think this article has a lot of information needed, looking forward to your new posts. Get permission to share

  • Your article has sparked a conversation that I believe is important and necessary.

Add a Comment

As it will appear on the website

Not displayed

Your website