Category Theory via C# (18) More Monad: IO<> 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

As mentioned in a previous part, in purely functional programming, functions cannot have side effects. For example, when defining LINQ queries, laziness and purity are expected. So, how should the impure actions be managed in purely functional programming or LINQ? For example:

  • Read from/write to console
  • Read from/write to file system
  • Download from/upload to Internet

etc. The IO<> monad is an approach.

IO<T> and impurity

The definition of IO<> is simple:

public delegate T IO<out T>();

Syntactically it is just Func<T>. However, it is used to represent a different semantic:

  • Here in category theory and functional programming, Func<T> is used to represent a pure function. When a Func<T> value is executed, it returns a T value without side effects
  • IO<T>  is used to represent a impure function. When a IO<T> function is applied, it returns a T value, with side effects.

So above examples can be represented with IO<T>

  • Read a line from console: Console.ReadLine: () → string  
    • Syntactically it is a Func<string>.
    • Now with IO<T>, semantically it can be represented as IO<string>, meaning when applied it returns a string value with side effect
  • Write a line to console: Console.WriteLIne: string → Void 
    • Syntactically it is an Action<string> or Func<string, Void>, since it takes a string parameter and returns nothing (Void)
    • Now semantically it can be a Func<string, IO<Void>>, meaning it is will eventually return nothing (a Void value) with side effect
      • Because C# does not allow using Void in that way, Console.WriteLIne will be represented by Func<string, IO<Unit>>, by borrowing Unit from F#.
      • Actually, in F# Console.WriteLine is of type string -> Unit
  • Read text from a file: File.ReadAllText: string → string 
    • Syntactically it is a Func<string, string>, since it takes a file path parameter and returns the text in that file
    • Now semantically it should be a Func<string, IO<string>>
  • Write text to a file: File.WriteAllText: (string, string) → Void 
    • Syntactically it is an Action<string, string> or Func<string, string, Void>, since it takes a file path parameter and a text parameter, and returns nothing (Void)
    • Now semantically it should be a Func<string, string, IO<Unit>>

etc. The following extension methods convert Func<T> to IO<T>, etc.:

[Pure]
public static partial class IOExtensions
{
    public static IO<Unit> AsIO
        (this Action action) => 
            () =>
                {
                    action();
                    return null;
                };

    public static Func<T, IO<Unit>> AsIO<T>
        (this Action<T> action) => arg => 
            () =>
                {
                    action(arg);
                    return null;
                };

    public static Func<T1, T2, IO<Unit>> AsIO<T1, T2>
        (this Action<T1, T2> action) => (arg1, arg2) => 
            () =>
                {
                    action(arg1, arg2);
                    return null;
                };

    public static Func<T1, T2, T3, IO<Unit>> AsIO<T1, T2, T3>
        (this Action<T1, T2, T3> action) => (arg1, arg2, arg3) => 
            () =>
                {
                    action(arg1, arg2, arg3);
                    return null;
                };

    public static Func<T1, T2, T3, T4, IO<Unit>> AsIO<T1, T2, T3, T4>
        (this Action<T1, T2, T3, T4> action) => (arg1, arg2, arg3, arg4) => 
            () =>
                {
                    action(arg1, arg2, arg3, arg4);
                    return null;
                };

    // ...

    public static IO<TResult> AsIO<TResult>
        (this Func<TResult> function) => 
            () => function();

    public static Func<T, IO<TResult>> AsIO<T, TResult>
        (this Func<T, TResult> function) => arg => 
            () => function(arg);

    public static Func<T1, T2, IO<TResult>> AsIO<T1, T2, TResult>
        (this Func<T1, T2, TResult> function) => (arg1, arg2) => 
            () => function(arg1, arg2);

    public static Func<T1, T2, T3, IO<TResult>> AsIO<T1, T2, T3, TResult>
        (this Func<T1, T2, T3, TResult> function) => (arg1, arg2, arg3) => 
            () => function(arg1, arg2, arg3);

    public static Func<T1, T2, T3, T4, IO<TResult>> AsIO<T1, T2, T3, T4, TResult>
        (this Func<T1, T2, T3, T4, TResult> function) => (arg1, arg2, arg3, arg4) => 
            () => function(arg1, arg2, arg3, arg4);

    // ...
}

so that:

IO<string> consoleReadLine = new Func<string>(Console.ReadLine).AsIO();
Func<string, IO<Unit>> consoleWriteLine = new Action<string>(Console.WriteLine).AsIO();

Func<string, IO<string>> fileReadAllText = new Func<string, string>(File.ReadAllText).AsIO();
Func<string, string, IO<Unit>> fileWriteAllText = new Action<string, string>(File.WriteAllText).AsIO();

Func<string, IO<bool>> fileExists = new Func<string, bool>(File.Exists).AsIO();
// ...

A lot of type information as usual. Some other functions can be created to make the code shorter:

[Pure]
public static partial class IO
{
    public static IO<Unit> Action
        (Action action) => action.AsIO();

    public static Func<T, IO<Unit>> Action<T>
        (this Action<T> action) => action.AsIO();

    public static Func<T1, T2, IO<Unit>> Action<T1, T2>
        (this Action<T1, T2> action) => action.AsIO();

    public static Func<T1, T2, T3, IO<Unit>> Action<T1, T2, T3>
        (this Action<T1, T2, T3> action) => action.AsIO();

    public static Func<T1, T2, T3, T4, IO<Unit>> Action<T1, T2, T3, T4>
        (this Action<T1, T2, T3, T4> action) => action.AsIO();

    // ...

    public static IO<T> Func<T>
        (this Func<T> function) => function.AsIO();

    public static Func<T, IO<TResult>> Func<T, TResult>
        (this Func<T, TResult> function) => function.AsIO();

    public static Func<T1, T2, IO<TResult>> Func<T1, T2, TResult>
        (this Func<T1, T2, TResult> function) => function.AsIO();

    public static Func<T1, T2, T3, IO<TResult>> Func<T1, T2, T3, TResult>
        (this Func<T1, T2, T3, TResult> function) => function.AsIO();

    public static Func<T1, T2, T3, T4, IO<TResult>> Func<T1, T2, T3, T4, TResult>
        (this Func<T1, T2, T3, T4, TResult> function) => function.AsIO();

    // ...
}

so that:

IO<string> consoleReadLine = IO.Func(Console.ReadLine);
Func<string, IO<Unit>> consoleWriteLine = IO.Action<string>(Console.WriteLine);

Func<string, IO<string>> fileReadAllText = IO.Func<string, string>(File.ReadAllText);
Func<string, string, IO<Unit>> fileWriteAllText = IO.Action<string, string>(File.WriteAllText);

Func<string, IO<bool>> fileExists = IO.Func<string, bool>(File.Exists);
// ...

Some type parameters are still needed for IO.Action/IO.Func to locate the specific overload.

IO<> monad

Again, for C# compiler, IO<> is exactly the same as Func<>, so IO<> must be a monad. The following SelectMany is copied from previous part of Func<> monad:

// [Pure]
public static partial class IOExtensions
{
    // Required by LINQ.
    public static IO<TResult> SelectMany<TSource, TSelector, TResult>
        (this IO<TSource> source, 
         Func<TSource, IO<TSelector>> selector, 
         Func<TSource, TSelector, TResult> resultSelector) => 
            () =>
                {
                    TSource sourceItem = source();
                    return resultSelector(sourceItem, selector(sourceItem)());
                };

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

The implementation for μ, φ, and ι are skipped since they are all the same as Func<>. Here is only Select implementation:

// [Pure]
public static partial class IOExtensions
{
    // η: T -> IO<T>
    public static IO<T> IO<T>
        (this T value) => () => value;

    // Select: (TSource -> TResult) -> (IO<TSource> -> IO<TResult>)
    public static IO<TResult> Select<TSource, TResult>
        (this IO<TSource> source, Func<TSource, TResult> selector) => 
            source.SelectMany(item => selector(item).IO());
}

Select has to be implemented so that let clause can be used in LINQ query:

                                 // 1. Read file name from console.
IO<Tuple<bool, string>> query1 = from fileName in IO.Func(Console.ReadLine)
                                 // 2. Write confirmation message to console.
                                 let message = string.Format(
                                                 CultureInfo.InstalledUICulture, "{0}? y/n", fileName)
                                 from _ in IO.Action<string>(Console.WriteLine)(message)
                                 // 3. Read confirmation from console.
                                 from confirmation in IO.Func(Console.ReadLine)
                                 // 4. If confirmed, read the file.
                                 let isConfirmed = string.Equals(
                                                 confirmation, "y", StringComparison.OrdinalIgnoreCase)
                                 from text in isConfirmed
                                                 ? IO.Func<string, string>(File.ReadAllText)(fileName)
                                                 : string.Empty.IO()
                                 // 5. Write text to console.
                                 from __ in IO.Action<string>(Console.WriteLine)(text)
                                 // 6. Returns text as query result.
                                 select new Tuple<bool, string>(isConfirmed, text); // Laziness.
Tuple<bool, string> result = query1(); // Execution.

Another example:

                  // 1. Read URL from console.
IO<Unit> query2 = from url in IO.Func(Console.ReadLine)
                  // 2. Download string from Internet.
                  from text in IO.Func(() => new WebClient().DownloadString(url))
                  // 3. Write string to console.
                  let length = 1000
                  let message = text.Length <= length 
                          ? text
                          : string.Format(CultureInfo.InstalledUICulture, "{0}...", text.Substring(0, length))
                  from unit in IO.Action<string>(Console.WriteLine)(message)
                  select (Unit)null; // Laziness.
query2(); // Execution...

Both examples demonstrated the purity and laziness of IO<> monad. When defining the LINQ query, those involved impure functions are not applied at all. They are applied only when the query is executed. Again, IO<> is exactly the same as Func<> at compile time and run time. It is just artificially assigned a different semantic from Func<>.

Monad laws, and unit tests

The following unit test demonstrates how IO<> monad satisfies the monad laws:

public partial class MonadTests
{
    [TestMethod()]
    public void IOTest()
    {
        bool isExecuted1 = false;
        bool isExecuted2 = false;
        bool isExecuted3 = false;
        bool isExecuted4 = false;
        IO<int> one = () => { isExecuted1 = true; return 1; };
        IO<int> two = () => { isExecuted2 = true; return 2; };
        Func<int, IO<int>> addOne = x => { isExecuted3 = true; return (x + 1).IO(); };
        Func<int, Func<int, IO<int>>> add = x => y => { isExecuted4 = true; return (x + y).IO(); };
        IO<IO<int>> query1 = from x in one
                                from y in two
                                from z in addOne.Partial(y)()
                                from _ in "abc".IO()
                                let addOne2 = add(x)
                                select addOne2(z);
        Assert.IsFalse(isExecuted1); // Laziness.
        Assert.IsFalse(isExecuted2); // Laziness.
        Assert.IsFalse(isExecuted3); // Laziness.
        Assert.IsFalse(isExecuted4); // Laziness.
        Assert.AreEqual(1 + 2 + 1, query1()()); // Execution.
        Assert.IsTrue(isExecuted1);
        Assert.IsTrue(isExecuted2);
        Assert.IsTrue(isExecuted3);
        Assert.IsTrue(isExecuted4);

        // Monad law 1: m.Monad().SelectMany(f) == f(m)
        Func<int, IO<int>> addOne3 = x => (x + 1).IO();
        IO<int> left = 1.IO().SelectMany(addOne3);
        IO<int> right = addOne3(1);
        Assert.AreEqual(left(), right());
        // Monad law 2: M.SelectMany(Monad) == M
        IO<int> M = 1.IO();
        left = M.SelectMany(m => m.IO());
        right = M;
        Assert.AreEqual(left(), right());
        // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2))
        Func<int, IO<int>> addTwo = x => (x + 2).IO();
        left = M.SelectMany(addOne3).SelectMany(addTwo);
        right = M.SelectMany(x => addOne3(x).SelectMany(addTwo));
        Assert.AreEqual(left(), right());

        bool isExecuted5 = false;
        bool isExecuted6 = false;
        bool isExecuted7 = false;
        Func<int, IO<int>> addOne4 = x => { isExecuted5 = true; return (x + 1).IO(); };
        Func<string, IO<int>> length = x => { isExecuted6 = true; return (x.Length).IO(); };
        Func<int, Func<int, IO<string>>> f7 = x => y =>
            { isExecuted7 = true; return (new string('a', x + y)).IO(); };
        Func<int, Func<string, IO<string>>> query2 = a => b => (from x in addOne4(a).IO()
                                                                from y in length(b).IO()
                                                                from z in 0.IO()
                                                                select f7(x())(y()))();
        Assert.IsFalse(isExecuted5); // Laziness.
        Assert.IsFalse(isExecuted6); // Laziness.
        Assert.IsFalse(isExecuted7); // Laziness.
        Assert.AreEqual(new string('a', 1 + 1 + "abc".Length), query2(1)("abc")()); // Execution.
        Assert.IsTrue(isExecuted5);
        Assert.IsTrue(isExecuted6);
        Assert.IsTrue(isExecuted7);
    }
}

8 Comments

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

  • It's too bad to check your article late. I wonder what it would be if we met a little faster. I want to exchange a little more, but please visit my site and leave a message!!

  • Category Theory via C# is an excellent topic that showcases the possibilities of this versatile programming language. The intersection of computer science and mathematics, particularly category theory, has huge potential for innovation and research. As an aspiring academic, I would recommend this topic for anyone looking to broaden their knowledge and contribute to the field. In case you need any help in [pay someone to do my assignment](https://www.britishessaywriters.co.uk/do-my-assignment/), there are several reliable services available that can assist you in crafting a high-quality paper on this subject.

  • It's the same topic , but I was quite surprised to see the opinions I didn't think of. My blog also has articles on these topics, so I look forward to your visit. <a href="https://google.im/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">safetoto</a>

  • Why couldn't I have the same or similar opinions as you? T^T I hope you also visit my blog and give us a good opinion. <a href="https://google.ie/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casino online</a>

  • Your article has answered the question I was wondering about! I would like to write a thesis on this subject, but I would like you to give your opinion once :D <a href="https://google.hu/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">totosite</a>

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="https://google.li/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccarat online</a>

  • To support the creators and the industry as a whole, it is advisable to opt for legal and authorized channels when accessing movies, ensuring fair compensation for the hard work and creativity invested in producing cinematic content.

Add a Comment

As it will appear on the website

Not displayed

Your website