1400 words
7 minutes
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 is a wrapper of Func and Task is a wrapper of Action. Actually Action can be viewed as Func, so that Task can be viewed as Task. Since C# compiler does not allow Void to be used in this way, Task can be just viewed as Task. 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, 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);
}
}
Category Theory via C# (17) Monad-like Tuple<>, Task<>, IQueryable<> And IQbservable<>
https://dixin.github.io/posts/category-theory-via-c-sharp-17-monad-like-tuple-task-iqueryable-and-iqbservable/
Author
Dixin
Published at
2018-12-18
License
CC BY-NC-SA 4.0