Category Theory via C# (20) More Monad: Reader< , > 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
Reader< , > Monad
Sometimes there are functions work with a shared environment. Typical examples are:
- Environment variables
- Application’s settings stored in App.config
- web application’s configurations stored in Web.config
The Reader< , > monad is a specialized State< , > monad. It threads an environment parameter through a sequence of functions.
The definition is simple:
// Reader<TEnvironment, T> is alias of Func<TEnvironment, T> public delegate T Reader<in TEnvironment, out T>(TEnvironment environment);
It is nothing but a Func< , >. This is its SelectMany:
[Pure] public static partial class ReaderExtensions { // Required by LINQ. public static Reader<TEnvironment, TResult> SelectMany<TEnvironment, TSource, TSelector, TResult> (this Reader<TEnvironment, TSource> source, Func<TSource, Reader<TEnvironment, TSelector>> selector, Func<TSource, TSelector, TResult> resultSelector) => environment => { TSource sourceResult = source(environment); return resultSelector(sourceResult, selector(sourceResult)(environment)); }; // Not required, just for convenience. public static Reader<TEnvironment, TResult> SelectMany<TEnvironment, TSource, TResult> (this Reader<TEnvironment, TSource> source, Func<TSource, Reader<TEnvironment, TResult>> selector) => source.SelectMany(selector, Functions.False); }
so that:
// [Pure] public static partial class ReaderExtensions { // μ: Reader<TEnvironment, Reader<TEnvironment, T>> => Reader<TEnvironment, T> public static Reader<TEnvironment, TResult> Flatten<TEnvironment, TResult> (Reader<TEnvironment, Reader<TEnvironment, TResult>> source) => source.SelectMany(Functions.Id); // η: T -> Reader<TEnvironment, T> public static Reader<TEnvironment, T> Reader<TEnvironment, T> (this T value) => environment => value; // φ: Lazy<Reader<TEnvironment, T1>, Reader<TEnvironment, T2>> => Reader<TEnvironment, Lazy<T1, T2>> public static Reader<TEnvironment, Lazy<T1, T2>> Binary<TEnvironment, T1, T2> (this Lazy<Reader<TEnvironment, T1>, Reader<TEnvironment, T2>> binaryFunctor) => binaryFunctor.Value1.SelectMany( value1 => binaryFunctor.Value2, (value1, value2) => new Lazy<T1, T2>(value1, value2)); // ι: TUnit -> Reader<TEnvironment, TUnit> public static Reader<TEnvironment, Unit> Unit<TEnvironment> (Unit unit) => unit.Reader<TEnvironment, Unit>(); // Select: (TSource -> TResult) -> (Reader<TEnvironment, TSource> -> Reader<TEnvironment, TResult>) public static Reader<TEnvironment, TResult> Select<TEnvironment, TSource, TResult> (this Reader<TEnvironment, TSource> source, Func<TSource, TResult> selector) => source.SelectMany(value => selector(value).Reader<TEnvironment, TResult>()); }
Here is an example of the usage in a .NET application:
Reader<Settings, string> query = // 1. Use settings. from html in new Reader<Settings, string>(settings => DownloadString(settings.BlogUrl)) // 2. Use settings. from _ in new Reader<Settings, Unit>(settings => SaveToDatabase(settings.ConnectionString, html)) // 3. Update settings. from __ in new Reader<Settings, Settings>(settings => UpdateSettings(settings)) // 4. Use settings. Here settings are updated. from ___ in new Reader<Settings, Unit>(settings => ListenToPort(settings.Port)) select html; string result = query(Settings.Default);
Monad laws, and unit tests
public partial class MonadTests { [TestMethod()] public void ReaderTest() { bool isExecuted1 = false; bool isExecuted2 = false; bool isExecuted3 = false; bool isExecuted4 = false; Reader<int, int> f1 = x => { isExecuted1 = true; return x + 1; }; Reader<int, string> f2 = x => { isExecuted2 = true; return x.ToString(CultureInfo.InvariantCulture); }; Func<string, Reader<int, int>> f3 = x => y => { isExecuted3 = true; return x.Length + y; }; Func<int, Func<int, int>> f4 = x => y => { isExecuted4 = true; return x + y; }; Reader<int, int> query1 = from x in f1 from y in f2 from z in f3(y) from _ in f1 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 + 1 + 1 + 1, query1(1)); // Execution. Assert.IsTrue(isExecuted1); Assert.IsTrue(isExecuted2); Assert.IsTrue(isExecuted3); Assert.IsTrue(isExecuted4); Tuple<bool, string> config = Tuple.Create(true, "abc"); // Monad law 1: m.Monad().SelectMany(f) == f(m) Func<int, Reader<Tuple<bool, string>, int>> addOne = x => c => x + 1; Reader<Tuple<bool, string>, int> left = 1.Reader<Tuple<bool, string>, int>().SelectMany(addOne); Reader<Tuple<bool, string>, int> right = addOne(1); Assert.AreEqual(left(config), right(config)); // Monad law 2: M.SelectMany(Monad) == M Reader<Tuple<bool, string>, int> M = c => 1 + c.Item2.Length; left = M.SelectMany(ReaderExtensions.Reader<Tuple<bool, string>, int>); right = M; Assert.AreEqual(left(config), right(config)); // Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2)) Func<int, Reader<Tuple<bool, string>, int>> addLength = x => c => x + c.Item2.Length; left = M.SelectMany(addOne).SelectMany(addLength); right = M.SelectMany(x => addOne(x).SelectMany(addLength)); Assert.AreEqual(left(config), right(config)); } }