language-ext v3.3.7 Release Notes

Release Date: 2019-08-08 // 13 days ago
  • 🔧 A common use for the Reader monad is to pass through a static environment. This can often be configuration, but it could also be a collection of functions for doing dependency injection (doing it well, rather than the OO way).

    For example:

    public interface IO { Seq\<string\> ReadAllLines(string path); Unit WriteAllLines(string path, Seq\<string\> lines); } public class RealIO : IO { public Seq\<string\> ReadAllLines(string path) =\> File.ReadAllLines(path).ToSeq(); public Unit WriteAllLines(string path, Seq\<string\> lines) { File.WriteAllLines(path, lines); return unit; } }

    This can then be used in a Reader computation:

    var comp = from io in ask\<IO\>() let ls = io.ReadAllLines("c:/test.txt") let \_= io.WriteAllLines("c:/test-copy.txt", ls) select ls.Count;

    🤡 Then the comp can be run with a real IO environment or a mocked one:

    comp.Run(new RealIO());

    However, carrying around the, non-changing, generic environment argument has a cognitive overhead and causes lots of extra typing.

    And so now it's possible to use the LanguageExt.CodeGen to wrap up the Reader<Env, A> into a simpler monad. i.e.

     [Reader(typeof(IO))] public partial struct Subsystem\<A\> { }

    NOTE: For now the new monadic type must be a struct

    NOTE ALSO: If you use multiple generic parameters then the last one will be the bound value type

    🏗 When providing the [Reader...] attribute with the type of the environment parameter, the code-gen will build:

    public partial struct Subsystem\<A\> { readonly LanguageExt.Reader\<TestBed.IO, A\> \_\_comp; internal Subsystem(LanguageExt.Reader\<TestBed.IO, A\> comp) =\> \_\_comp = comp; public static Subsystem\<A\> Return(A value) =\> new Subsystem\<A\>(env =\> (value, false)); public static Subsystem\<A\> Fail =\> new Subsystem\<A\>(env =\> (default, true)); public Subsystem\<B\> Map\<B\>(Func\<A, B\> f) =\> new Subsystem\<B\>(\_\_comp.Map(f)); public Subsystem\<B\> Select\<B\>(Func\<A, B\> f) =\> new Subsystem\<B\>(\_\_comp.Map(f)); public Subsystem\<B\> SelectMany\<B\>(Func\<A, Subsystem\<B\>\> f) =\> new Subsystem\<B\>(\_\_comp.Bind(a =\> f(a).\_\_comp)); public Subsystem\<C\> SelectMany\<B, C\>(Func\<A, Subsystem\<B\>\> bind, Func\<A, B, C\> project) =\> new Subsystem\<C\>(\_\_comp.Bind(a =\> bind(a).\_\_comp.Map(b =\> project(a, b)))); public Subsystem\<TestBed.IO\> Ask =\> new Subsystem\<TestBed.IO\>(LanguageExt.Prelude.ask\<TestBed.IO\>()); public TryOption\<A\> Run(TestBed.IO env) =\> \_\_comp.Run(env); public Subsystem\<A\> Where(Func\<A, bool\> f) =\> new Subsystem\<A\>(\_\_comp.Where(f)); public Subsystem\<A\> Filter(Func\<A, bool\> f) =\> new Subsystem\<A\>(\_\_comp.Filter(f)); public Subsystem\<A\> Do(Action\<A\> f) =\> new Subsystem\<A\>(\_\_comp.Do(f)); public Subsystem\<A\> Strict() =\> new Subsystem\<A\>(\_\_comp.Strict()); public Seq\<A\> ToSeq(TestBed.IO env) =\> \_\_comp.ToSeq(env); public Subsystem\<LanguageExt.Unit\> Iter(Action\<A\> f) =\> new Subsystem\<LanguageExt.Unit\>(\_\_comp.Iter(f)); public Func\<TestBed.IO, S\> Fold\<S\>(S state, Func\<S, A, S\> f) { var self = this; return env =\> self.\_\_comp.Fold(state, f).Run(env).IfNoneOrFail(state); } public Func\<TestBed.IO, bool\> ForAll\<S\>(S state, Func\<A, bool\> f) { var self = this; return env =\> self.\_\_comp.ForAll(f).Run(env).IfNoneOrFail(false); } public Func\<TestBed.IO, bool\> Exists\<S\>(S state, Func\<A, bool\> f) { var self = this; return env =\> self.\_\_comp.Exists(f).Run(env).IfNoneOrFail(false); } }public static partial class Subsystem{ public static Subsystem\<A\> Return\<A\>(A value) =\> Subsystem\<A\>.Return(value); public static Subsystem\<A\> Fail\<A\>() =\> Subsystem\<A\>.Fail; public static Subsystem\<A\> asks\<A\>(Func\<TestBed.IO, A\> f) =\> new Subsystem\<A\>(env =\> (f(env), false)); public static readonly Subsystem\<TestBed.IO\> ask = new Subsystem\<TestBed.IO\>(env =\> (env, false)); }

    This then allows for the example code above to become:

    var comp = from io in Subsystem.asklet ls = io.ReadAllLines("c:/test.txt") let \_= io.WriteAllLines("c:/test-copy.txt", ls) select ls.Count;

    👀 As you can see it has provided wrappers for all of the useful functions of the Reader monad into two partial types that can then easily be extended. This makes it much easier to work with the Reader: the generated monad will behave almost exactly like any of the other simpler monads like Option, but will have access to a 'hidden' environment.

    This is the first step to simplifying the use of Reader<Env, A>, Writer<MonoidW, W, A>, State<S, A>, and RWS<MonoidW, R, W, S, A>. The future versions will also generate the Monad class-instance, as well as generate the transformer stack for easy nesting of the new monad.