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);
    }
}

1 Comment

Add a Comment

As it will appear on the website

Not displayed

Your website