Category Theory via C# (16) More Monads: Lazy<>, Func<>, Nullable<>, ParallelQuery<> And IObservale<>
[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
Lazy<> monad
Again, Lazy<> is the simplest monad, it is just the lazy version of Tuple<>, and should be considered as the Id<> monad. This is the implementation of its SelectMany:
// [Pure] public static partial class LazyExtensions { // Required by LINQ. public static Lazy<TResult> SelectMany<TSource, TSelector, TResult> (this Lazy<TSource> source, Func<TSource, Lazy<TSelector>> selector, Func<TSource, TSelector, TResult> resultSelector) => new Lazy<TResult>(() => resultSelector(source.Value, selector(source.Value).Value)); // Not required, just for convenience. public static Lazy<TResult> SelectMany<TSource, TResult> (this Lazy<TSource> source, Func<TSource, Lazy<TResult>> selector) => source.SelectMany(selector, Functions.False); }
So that Lazy<> is a monad, a monoidal functor, and a functor:
// [Pure] public static partial class LazyExtensions { // μ: Lazy<Lazy<T> => Lazy<T> public static Lazy<TResult> Flatten<TResult> (this Lazy<Lazy<TResult>> source) => source.SelectMany(Functions.Id); // η: T -> Lazy<T> is already implemented previously as LazyExtensions.Lazy. // φ: Lazy<Lazy<T1>, Lazy<T2>> => Lazy<Lazy<T1, T2>> public static Lazy<Lazy<T1, T2>> Binary2<T1, T2> (this Lazy<Lazy<T1>, Lazy<T2>> binaryFunctor) => binaryFunctor.Value1.SelectMany( value1 => binaryFunctor.Value2, (value1, value2) => new Lazy<T1, T2>(value1, value2)); // ι: TUnit -> Lazy<TUnit> is already implemented previously with η: T -> Lazy<T>. // Select: (TSource -> TResult) -> (Lazy<TSource> -> Lazy<TResult>) public static Lazy<TResult> Select2<TSource, TResult> (this Lazy<TSource> source, Func<TSource, TResult> selector) => source.SelectMany(value => selector(value).Lazy()); }
Lazy<> is similar to the Haskell Id Monad. The usage will be demonstrated in unit tests later.
Func<> monad
SelectMany can be implemented for Func<> too:
// [Pure] public static partial class FuncExtensions { // Required by LINQ. public static Func<TResult> SelectMany<TSource, TSelector, TResult> (this Func<TSource> source, Func<TSource, Func<TSelector>> selector, Func<TSource, TSelector, TResult> resultSelector) => () => { TSource sourceValue = source(); return resultSelector(sourceValue, selector(sourceValue)()); }; // Not required, just for convenience. public static Func<TResult> SelectMany<TSource, TResult> (this Func<TSource> source, Func<TSource, Func<TResult>> selector) => source.SelectMany(selector, Functions.False); }
So that Func<> is a monad, a monoidal functor, and a functor:
// [Pure] public static partial class FuncExtensions { // μ: Func<Func<T> => Func<T> public static Func<TResult> Flatten<TResult> (this Func<Func<TResult>> source) => source.SelectMany(Functions.Id); // η: T -> Func<T> is already implemented previously as FuncExtensions.Func. // φ: Lazy<Func<T1>, Func<T2>> => Func<Lazy<T1, T2>> public static Func<Lazy<T1, T2>> Binary2<T1, T2> (this Lazy<Func<T1>, Func<T2>> binaryFunctor) => binaryFunctor.Value1.SelectMany( value1 => binaryFunctor.Value2, (value1, value2) => new Lazy<T1, T2>(value1, value2)); // ι: TUnit -> Func<TUnit> is already implemented previously with η: T -> Func<T>. // Select: (TSource -> TResult) -> (Func<TSource> -> Func<TResult>) public static Func<TResult> Select2<TSource, TResult> (this Func<TSource> source, Func<TSource, TResult> selector) => source.SelectMany(value => selector(value).Func()); }
Nullable<> monad
And this is the SelectMany for Nullable<>:
// [Pure] public static partial class NullableExtensions { // Required by LINQ. public static Nullable<TResult> SelectMany<TSource, TSelector, TResult> (this Nullable<TSource> source, Func<TSource, Nullable<TSelector>> selector, Func<TSource, TSelector, TResult> resultSelector) => new Nullable<TResult>(() => { if (source.HasValue) { Nullable<TSelector> selectorResult = selector(source.Value); if (selectorResult.HasValue) { return Tuple.Create(true, resultSelector(source.Value, selectorResult.Value)); } } return Tuple.Create(false, default(TResult)); }); // Not required, just for convenience. public static Nullable<TResult> SelectMany<TSource, TResult> (this Nullable<TSource> source, Func<TSource, Nullable<TResult>> selector) => source.SelectMany(selector, Functions.False); }
So that Nullable<> is a monad, a monoidal functor, and a functor:
// [Pure] public static partial class NullableExtensions { // μ: Nullable<Nullable<T> => Nullable<T> public static Nullable<TResult> Flatten<TResult> (this Nullable<Nullable<TResult>> source) => source.SelectMany(Functions.Id); // η: T -> Nullable<T> is already implemented previously as NullableExtensions.Nullable. // φ: Lazy<Nullable<T1>, Nullable<T2>> => Nullable<Lazy<T1, T2>> public static Nullable<Lazy<T1, T2>> Binary2<T1, T2> (this Lazy<Nullable<T1>, Nullable<T2>> binaryFunctor) => binaryFunctor.Value1.SelectMany( value1 => binaryFunctor.Value2, (value1, value2) => new Lazy<T1, T2>(value1, value2)); // ι: TUnit -> Nullable<TUnit> is already implemented previously with η: T -> Nullable<T>. // Select: (TSource -> TResult) -> (Nullable<TSource> -> Nullable<TResult>) public static Nullable<TResult> Select2<TSource, TResult> (this Nullable<TSource> source, Func<TSource, TResult> selector) => source.SelectMany(value => selector(value).Nullable()); }
ParallelQuery<> monad
Parallel LINQ provides ParallelQuery<> monad. This is its SelectMany:
[Pure] public static class ParallelEnumerable { public static ParallelQuery<TResult> SelectMany<TSource, TCollection, TResult> (this ParallelQuery<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) => new SelectManyQueryOperator<TSource, TCollection, TResult>( source, collectionSelector, null, resultSelector); }
The implementation of System.Linq.Parallel.SelectManyQueryOperator<TLeftInput, TRightInput, TOutput> class is a big parallel computing topic and will be skipped in these category theory posts.
ParallelQuery<> can be used with laziness and purity:
ParallelQuery<int> query = from value in Enumerable.Range(0, 1000).AsParallel() from repeat in Enumerable.Repeat(value, 2) select repeat; // Laziness. query.ForAll(Console.WriteLine); // Execution.
Above code is just an example of using ParallelQuery<> monad with LINQ syntax. In real world parallel query, some performance pitfalls has to be noticed.
IObservable<> monad
IObservable<> is built-in in mscorlib. Then Rx (Reactive Extensions) makes IObservable<> a monad. In System.Reactive.Linq.dll, SelectMany is implemented for IObservable<>:
public static class Observable { public static IObservable<TResult> SelectMany<TSource, TCollection, TResult> (this IObservable<TSource> source, Func<TSource, IObservable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector) => new SelectMany<TSource, TCollection, TResult>( source, collectionSelector, resultSelector); }
System.Reactive.Linq.ObservableImpl.SelectMany<TSource, TCollection, TResult> class’s source code can be viewed on github here.
So IObservable becomes a monad andcan be used in LINQ with laziness and purity:
IObservable<int> query = from value in Observable.Range(0, 1000) from repeat in Observable.Repeat(value, 2) select repeat; // Laziness. query = query.Do(value => Console.WriteLine("Do")); // Laziness. query.Subscribe(Console.WriteLine); // Execution.
Another example is the MouseDrag event implementation in WPF:
[Pure] public static class UIElementExtensions { public static IObservable<EventPattern<MouseEventArgs>> MouseDrag (this UIElement element) => from _ in Observable.FromEventPattern<MouseEventArgs>(element, nameof(element.MouseDown)) from @event in Observable.FromEventPattern<MouseEventArgs>(element, nameof(element.MouseMove)) .TakeUntil(Observable.FromEventPattern<MouseEventArgs>(element, nameof(element.MouseUp))) select @event; }
The observing starts from MouseDown event, then keep taking MouseMove event, until MouseUp event is observed. So that:
element.MouseDrag()
.Subscribe(@event => OnMouseDrag(@event.EventArgs));
Unit tests
public partial class MonadTests { [TestMethod()] public void LazyTest() { bool isExecuted1 = false; bool isExecuted2 = false; bool isExecuted3 = false; Lazy<int> one = new Lazy<int>(() => { isExecuted1 = true; return 1; }); Lazy<int> two = new Lazy<int>(() => { isExecuted2 = true; return 2; }); Func<int, Func<int, int>> add = x => y => { isExecuted3 = true; return x + y; }; Lazy<int> query = from x in one from y in two from _ in one select add(x)(y); Assert.IsFalse(isExecuted1); // Laziness. Assert.IsFalse(isExecuted2); // Laziness. Assert.IsFalse(isExecuted3); // Laziness. Assert.AreEqual(1 + 2, query.Value); // Execution. Assert.IsTrue(isExecuted1); Assert.IsTrue(isExecuted2); Assert.IsTrue(isExecuted3); // Monad law 1: m.Monad().SelectMany(f) == f(m) Func<int, Lazy<int>> addOne = x => (x + 1).Lazy(); Lazy<int> left = 1.Lazy().SelectMany(addOne); Lazy<int> right = addOne(1); Assert.AreEqual(left.Value, right.Value); // Monad law 2: M.SelectMany(Monad) == M Lazy<int> M = 1.Lazy(); left = M.SelectMany(LazyExtensions.Lazy); right = M; Assert.AreEqual(left.Value, right.Value); // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2)) Func<int, Lazy<int>> addTwo = x => (x + 2).Lazy(); left = M.SelectMany(addOne).SelectMany(addTwo); right = M.SelectMany(x => addOne(x).SelectMany(addTwo)); Assert.AreEqual(left.Value, right.Value); } [TestMethod()] public void FuncTest() { bool isExecuted1 = false; bool isExecuted2 = false; bool isExecuted3 = false; bool isExecuted4 = false; Func<int> f1 = () => { isExecuted1 = true; return 1; }; Func<int> f2 = () => { isExecuted2 = true; return 2; }; Func<int, int> f3 = x => { isExecuted3 = true; return x + 1; }; Func<int, Func<int, int>> f4 = x => y => { isExecuted4 = true; return x + y; }; Func<int> query1 = from x in f1 from y in f2 from z in f3.Partial(y) from _ in "abc".Func() let f4x = f4(x) select f4x(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, Func<int>> addOne = x => (x + 1).Func(); Func<int> left = 1.Func().SelectMany(addOne); Func<int> right = addOne(1); Assert.AreEqual(left(), right()); // Monad law 2: M.SelectMany(Monad) == M Func<int> M = 1.Func(); left = M.SelectMany(FuncExtensions.Func); right = M; Assert.AreEqual(left(), right()); // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2)) Func<int, Func<int>> addTwo = x => (x + 2).Func(); left = M.SelectMany(addOne).SelectMany(addTwo); right = M.SelectMany(x => addOne(x).SelectMany(addTwo)); Assert.AreEqual(left(), right()); bool isExecuted5 = false; bool isExecuted6 = false; bool isExecuted7 = false; Func<int, int> f5 = x => { isExecuted5 = true; return x + 1; }; Func<string, int> f6 = x => { isExecuted6 = true; return x.Length; }; Func<int, Func<int, string>> f7 = x => y => { isExecuted7 = true; return new string('a', x + y); }; Func<int, Func<string, string>> query2 = a => b => (from x in f5(a).Func() from y in f6(b).Func() from z in 0.Func() 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); } [TestMethod()] public void NullableTest() { bool isExecuted1 = false; Func<int, Func<int, string>> add = x => y => { isExecuted1 = true; return (x + y).ToString(CultureInfo.InvariantCulture); }; Nullable<int> nullable1 = new Nullable<int>(); Nullable<int> nullable2 = new Nullable<int>(); Nullable<string> query1 = from x in nullable1 from y in nullable2 from _ in nullable1 select add(x)(y); Assert.IsFalse(isExecuted1); // Laziness. Assert.IsFalse(query1.HasValue); // Execution. Assert.IsFalse(isExecuted1); bool isExecuted3 = false; bool isExecuted4 = false; bool isExecuted5 = false; add = x => y => { isExecuted3 = true; return (x + y).ToString(CultureInfo.InvariantCulture); }; Nullable<int> one = new Nullable<int>(() => { isExecuted4 = true; return Tuple.Create(true, 1); }); Nullable<int> two = new Nullable<int>(() => { isExecuted5 = true; return Tuple.Create(true, 2); }); Nullable<string> query2 = from x in one from y in two from _ in one select add(x)(y); Assert.IsFalse(isExecuted3); // Laziness. Assert.IsFalse(isExecuted4); // Laziness. Assert.IsFalse(isExecuted5); // Laziness. Assert.IsTrue(query2.HasValue); // Execution. Assert.AreEqual("3", query2.Value); Assert.IsTrue(isExecuted3); Assert.IsTrue(isExecuted4); Assert.IsTrue(isExecuted5); // Monad law 1: m.Monad().SelectMany(f) == f(m) Func<int, Nullable<int>> addOne = x => (x + 1).Nullable(); Nullable<int> left = 1.Nullable().SelectMany(addOne); Nullable<int> right = addOne(1); Assert.AreEqual(left.Value, right.Value); // Monad law 2: M.SelectMany(Monad) == M Nullable<int> M = 1.Nullable(); left = M.SelectMany(NullableExtensions.Nullable); right = M; Assert.AreEqual(left.Value, right.Value); // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2)) Func<int, Nullable<int>> addTwo = x => (x + 2).Nullable(); left = M.SelectMany(addOne).SelectMany(addTwo); right = M.SelectMany(x => addOne(x).SelectMany(addTwo)); Assert.AreEqual(left.Value, right.Value); } }