Category Theory via C# (17) Monad-like Tuple<>, Task<>, IQueryable<> And IQbservable<>

[LINQ via C# series]

[Category Theory via C# series]

Latest version: https://weblogs.asp.net/dixin/category-theory-via-csharp-7-monad-and-linq-to-monads

Tuple<>: lack of laziness

Theoretically, Tuple<> should be counted as the Id<> monad. However, it is lack of laziness. In the context of C# and LINQ, it is only monad-like.

This is its SelectMany:

// [Pure]
public static partial class TupleExtensions
{
    // Required by LINQ.
    public static Tuple<TResult> SelectMany<TSource, TSelector, TResult>
        (this Tuple<TSource> source,
         Func<TSource, Tuple<TSelector>> selector,
         Func<TSource, TSelector, TResult> resultSelector) => 
            new Tuple<TResult>(resultSelector(source.Item1, selector(source.Item1).Item1));

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

which can implement μ, η, φ, ι, Select:

// [Pure]
public static partial class TupleExtensions
{
    // μ: Tuple<Tuple<T> => Tuple<T>
    public static Tuple<TResult> Flatten<TResult>
        (this Tuple<Tuple<TResult>> source) => source.SelectMany(Functions.Id);

    // η: T -> Tuple<T> is already implemented previously as TupleExtensions.Tuple.

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

    // ι: TUnit -> Tuple<TUnit> is already implemented previously with η: T -> Tuple<T>.

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

Tuple<> is most close to the Haskell Id Monad.

Task<>: lack of purity

Task<> also seems monadic, but is lack of purity. This is the SelectMany for Task<>:

// Impure.
public static partial class TaskExtensions
{
    // Required by LINQ.
    public static async Task<TResult> SelectMany<TSource, TSelector, TResult>
        (this Task<TSource> source,
         Func<TSource, Task<TSelector>> selector,
         Func<TSource, TSelector, TResult> resultSelector) => 
            resultSelector(await source, await selector(await source));

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

which can implement μ, η, φ, ι, Select:

// Impure.
public static partial class TaskExtensions
{
    // μ: Task<Task<T> => Task<T>
    public static Task<TResult> Flatten<TResult>
        (this Task<Task<TResult>> source) => source.SelectMany(Functions.Id);

    // η: T -> Task<T> is already implemented previously as TaskExtensions.Task.

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

    // ι: TUnit -> Task<TUnit> is already implemented previously with η: T -> Task<T>.

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

Task<> and LINQ

With above SelectMany, Task<> can be used in LINQ syntax:

Func<string, Task<string>> query = url =>
    from httpResponseMessage in new HttpClient().GetAsync(url) // Returns Task<HttpResponseMessage>
    from html in httpResponseMessage.Content.ReadAsStringAsync() // Returns Task<string>
    select html;
string result = await query("https://weblogs.asp.net/dixin");

Non-generic Task

Task<T> is a wrapper of Func<T> and Task is a wrapper of Action. Actually Action can be viewed as Func<Void>, so that Task can be viewed as Task<Void>. Since C# compiler does not allow Void to be used in this way, Task can be just viewed as Task<Unit>. In this way, Task become like monad too.

// Impure.
public static partial class TaskExtensions
{
    // Required by LINQ.
    public static async Task<TResult> SelectMany<TSelector, TResult>(
        this Task source,
        Func<Unit, Task<TSelector>> selector,
        Func<Unit, TSelector, TResult> resultSelector)
    {
        await source;
        return resultSelector(null, await selector(null));
    }

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

so that

// Impure.
public static partial class TaskExtensions
{
    // η: Unit -> Task.
    public static Task Task(Unit unit) => System.Threading.Tasks.Task.Run(() => { });

    // ι: TUnit -> Task is already implemented previously with η: Unit -> Task.

    // Select: (Unit -> TResult) -> (Task -> Task<TResult>)
    public static Task<TResult> Select<TResult>
        (this Task source, Func<Unit, TResult> selector) => source.SelectMany(value => selector(value).Task());
}

IQueryable<> is like a monad

IQueryable<> has been discussed a lot in previous posts. It looks like monad, with laziness and purity:

using (NorthwindDataContext database = new NorthwindDataContext())
{
    var query = from category in database.Categories
                from product in category.Products
                select new { category.CategoryName, product.ProductName }; // Laziness

    query.ForEach(value => { }); // Execution.
}

Or equivalently:

using (NorthwindDataContext database = new NorthwindDataContext())
{
    var query = database.Categories.SelectMany(
        category => category.Products, 
        (category, product) => new { category.CategoryName, product.ProductName }); // Laziness

    query.ForEach(value => { }); // Execution.
}

However, this is its SelectMany implementation:

// [Pure]
public static partial class QueryableExtensions
{
    public static IQueryable<TResult> SelectMany<TSource, TCollection, TResult>
        (this IQueryable<TSource> source,
         Expression<Func<TSource, IEnumerable<TCollection>>> collectionSelector,
         Expression<Func<TSource, TCollection, TResult>> resultSelector) => 
            source.Provider.CreateQuery<TResult>(Expression.Call(
                null, 
                ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource), typeof(TCollection), typeof(TResult) }),
                new Expression[]
                    {
                        source.Expression,
                        Expression.Quote(collectionSelector),
                        Expression.Quote(resultSelector)
                    }));

    public static IQueryable<TResult> SelectMany<TSource, TResult>
        (this IQueryable<TSource> source,
         Expression<Func<TSource, IEnumerable<TResult>>> selector) => 
            source.Provider.CreateQuery<TResult>(Expression.Call(
                null, 
                ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource), typeof(TResult) }),
                new Expression[] { source.Expression, Expression.Quote(selector) }));
}

As discussed before,  when working with IQueryable<T>, the lambda expressions are not functions but data structure - an abstract syntax tree. So that a lambda-like expression trees in the query can be compiled to something else - here a T-SQL query:

SELECT [t0].[CategoryName], [t1].[ProductName]
FROM [dbo].[Categories] AS [t0], [dbo].[Products] AS [t1]
WHERE [t1].[CategoryID] = [t0].[CategoryID]

This is a very powerful feature of C# language and LINQ.

IQbservable<> is also like a monad

IQbservable<> is provided by System.Reactive.Interfaces, a part of Rx (Reactive Extensions). It is the queryable version of IObservable<>, works similarly with expression lambda-like expression trees.

Here are 2 samples of Qbservable providers:

Unit tests

Following unit tests demonstrate the usage of monadic Tuple<> and Task<>. Notice Tuple is lack of laziness, and Task<>’s SelectMany extension method work for both cold tasks and hot tasks.

public partial class MonadTests
{
    [TestMethod()]
    public void TupleTest()
    {
        bool isExecuted = false;
        Tuple<int> one = new Tuple<int>(1);
        Tuple<int> two = new Tuple<int>(2);
        Func<int, Func<int, int>> add = x => y => { isExecuted = true; return x + y; };
        Tuple<int> query = from x in one
                            from y in two
                            from _ in one
                            select add(x)(y);
        Assert.IsTrue(isExecuted); // No laziness.
        Assert.AreEqual(1 + 2, query.Item1); // Execution.

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

    [TestMethod()]
    public void HotTaskTest()
    {
        Task<string> a = Task.Run(() => "a");
        Task<string> b = Task.Run(() => "b");
        Func<string, Func<string, string>> concat = x => y => x + y;
        Task<string> query1 = from x in a
                                from y in b
                                from _ in a
                                select concat(x)(y);
        Assert.AreEqual("a" + "b", query1.Result);

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

    [TestMethod()]
    public void ColdTaskTest()
    {
        bool isExecuted1 = false;
        bool isExecuted2 = false;
        bool isExecuted3 = false;
        Task<string> a = new Task<string>(() => { isExecuted1 = true; return "a"; });
        Task<string> b = new Task<string>(() => { isExecuted2 = true; return "b"; });
        Func<string, Func<string, string>> concat = x => y => { isExecuted3 = true; return x + y; };
        Task<string> query = from x in a
                                from y in b
                                from _ in a
                                select concat(x)(y);
        Assert.IsFalse(isExecuted1); // Laziness.
        Assert.IsFalse(isExecuted2); // Laziness.
        Assert.IsFalse(isExecuted3); // Laziness.
        a.Start(); // Execution.
        b.Start(); // Execution.
        Assert.AreEqual("a" + "b", query.Result);
        Assert.IsTrue(isExecuted1);
        Assert.IsTrue(isExecuted2);
        Assert.IsTrue(isExecuted3);

        // Monad law 1: m.Monad().SelectMany(f) == f(m)
        List<Task<int>> addOneTasks = new List<Task<int>>();
        Func<int, Task<int>> addOne = x =>
        {
            Task<int> task = (x + 1).Task(true);
            addOneTasks.Add(task);
            return task;
        };
        Task<int> one = 1.Task(true);
        Task<int> left = one.SelectMany(addOne);
        Task<int> right = addOne(1);
        one.Start();
        while (addOneTasks.Count < 2) { }
        addOneTasks.ForEach(task => task.Start());
        Assert.AreEqual(left.Result, right.Result);
        // Monad law 2: M.SelectMany(Monad) == M
        Task<int> M = 1.Task(true);
        left = M.SelectMany(TaskExtensions.Task);
        right = M;
        M.Start();
        Assert.AreEqual(left.Result, right.Result);
        // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2))
        addOneTasks.Clear();
        List<Task<int>> addTwoTasks = new List<Task<int>>();
        M = 1.Task(true);
        Func<int, Task<int>> addTwo = x =>
        {
            Task<int> task = (x + 1).Task(true);
            addTwoTasks.Add(task);
            return task;
        };
        left = M.SelectMany(addOne).SelectMany(addTwo);
        right = M.SelectMany(x => addOne(x).SelectMany(addTwo));
        M.Start();
        while (addOneTasks.Count < 2) { }
        addOneTasks.ForEach(task => task.Start());
        while (addTwoTasks.Count < 2) { }
        addTwoTasks.ForEach(task => task.Start());
        Assert.AreEqual(left.Result, right.Result);
    }
}

2 Comments

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

  • The assignment writing platforms provide crucial academic support and learning resources for international students. They can address language barriers, offer professional knowledge assistance, save time and energy, and aid international students in improving their academic grades. However, international students should understand the proper usage of assignment writing platforms and maintain academic integrity while using them. These platforms should be seen as learning aids rather than substitutes for independent studying by international students. By utilizing assignment writing platforms correctly, international students can gain more academic support and learning opportunities, enhancing their academic skills and performance levels.

Add a Comment

As it will appear on the website

Not displayed

Your website