language-ext v3.3.10 Release Notes

Release Date: 2019-08-14 // over 4 years ago
  • ๐Ÿš€ Following on from the last two releases:

    The LanguageExt.CodeGen system now supports wrapping the Reader/Writer/State (RWS) monad.

    Usage of the RWS monad is complicated because it takes so many generic arguments:

    RWS\<MonoidW, R, W, S, A\> where MonoidW : struct, Monoid\<W\>
    

    And so you can be forgiven for not giving it a try. For, the unitiated, the RWS monad is a super-powered monad that combines the features of the:

    • Reader monad - in that is can take an environment. Think of this as read-only configuration state. This is useful to keep functions pure when working with 'global' state.
    • ๐ŸŒฒ Writer monad - which maintains a state that is a monoid. The most common monoid to use here would be a Seq<W>. The writer monad allows for easy logging of items without needing access to an external log. Again, keeping the computation pure. But can also be used to sum totals, concatenate strings, etc.
    • โšก๏ธ State monad - which is like the Reader monad in that it manages a state object, but the state can be updated as the computation runs.

    And so all of these features in a single monad provides a very powerful set of tools for writing pure functions. The unfortunate aspect of this is that the majority of the generic parameters are fixed for the lifetime of the computation but need to be provided far too regularly due to C#'s poor type-inference story: MonoidW, R, W. And even S could be fixed depending on its use-case.

    ๐Ÿš€ And so this is where this release comes in, a code-gen system that wraps the RWS monad into something a bit more manageable.

     [RWS(WriterMonoid: typeof(MSeq\<string\>), Env: typeof(IO))] public partial struct Subsys\<S, T\> {}
    

    โšก๏ธ The code above will create a wrapped RWS monad with an S state that can vary (by calling put(x)). Below, is a version that doesn't have a variable S generic parameter, and instead has a fixed state embedded in the wrapped RWS monad. So, put can only be called with another Person to update the state.

     [RWS(WriterMonoid: typeof(MSeq\<string\>), Env: typeof(IO), State: typeof(Person))] public partial struct Subsys\<T\> { }
    

    The generated code for the second option looks like this:

    public partial struct Subsys\<T\> { readonly internal LanguageExt.RWS\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\> \_\_comp; internal Subsys(LanguageExt.RWS\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\> comp) =\> \_\_comp = comp; public static Subsys\<T\> Pure(T value) =\> new Subsys\<T\>((env, state) =\> (value, default, state, false)); public static Subsys\<T\> Fail =\> new Subsys\<T\>((env, state) =\> (default, default, default, true)); public Subsys\<U\> Map\<U\>(Func\<T, U\> f) =\> new Subsys\<U\>(\_\_comp.Map(f)); public Subsys\<U\> Select\<U\>(Func\<T, U\> f) =\> new Subsys\<U\>(\_\_comp.Map(f)); public Subsys\<U\> Bind\<U\>(Func\<T, Subsys\<U\>\> f) =\> new Subsys\<U\>(\_\_comp.Bind(a =\> f(a).\_\_comp)); public Subsys\<U\> SelectMany\<U\>(Func\<T, Subsys\<U\>\> f) =\> new Subsys\<U\>(\_\_comp.Bind(a =\> f(a).\_\_comp)); public Subsys\<V\> SelectMany\<U, V\>(Func\<T, Subsys\<U\>\> bind, Func\<T, U, V\> project) =\> new Subsys\<V\>(\_\_comp.Bind(a =\> bind(a).\_\_comp.Map(b =\> project(a, b)))); public (TryOption\<T\> Value, LanguageExt.Seq\<string\> Output, TestBed.Person State) Run(TestBed.IO env, TestBed.Person state) =\> \_\_comp.Run(env, state); public Subsys\<T\> Filter(Func\<T, bool\> f) =\> new Subsys\<T\>(\_\_comp.Where(f)); public Subsys\<T\> Where(Func\<T, bool\> f) =\> new Subsys\<T\>(\_\_comp.Where(f)); public Subsys\<T\> Do(Action\<T\> f) =\> new Subsys\<T\>(\_\_comp.Do(f)); public Subsys\<T\> Strict() =\> new Subsys\<T\>(\_\_comp.Strict()); public Seq\<T\> ToSeq(TestBed.IO env, TestBed.Person state) =\> \_\_comp.ToSeq(env, state); public Subsys\<LanguageExt.Unit\> Iter(Action\<T\> f) =\> new Subsys\<LanguageExt.Unit\>(\_\_comp.Iter(f)); public Func\<TestBed.IO, TestBed.Person, State\> Fold\<State\>(State state, Func\<State, T, State\> f) { var self = this; return (env, s) =\> self.\_\_comp.Fold(state, f).Run(env, s).Value.IfNoneOrFail(state); } public Func\<TestBed.IO, TestBed.Person, bool\> ForAll(Func\<T, bool\> f) { var self = this; return (env, s) =\> self.\_\_comp.ForAll(f).Run(env, s).Value.IfNoneOrFail(false); } public Func\<TestBed.IO, TestBed.Person, bool\> Exists(Func\<T, bool\> f) { var self = this; return (env, s) =\> self.\_\_comp.Exists(f).Run(env, s).Value.IfNoneOrFail(false); } public Subsys\<T\> Local(Func\<TestBed.IO, TestBed.IO\> f) =\> new Subsys\<T\>(LanguageExt.Prelude.local\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, T\>(\_\_comp, f)); public Subsys\<(T, U)\> Listen\<U\>(Func\<LanguageExt.Seq\<string\>, U\> f) =\> new Subsys\<(T, U)\>(\_\_comp.Listen(f)); public Subsys\<T\> Censor(Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\> f) =\> new Subsys\<T\>(\_\_comp.Censor(f)); } public static partial class Subsys { public static Subsys\<T\> Pure\<T\>(T value) =\> Subsys\<T\>.Pure(value); public static Subsys\<T\> Fail\<T\>() =\> Subsys\<T\>.Fail; public static Subsys\<T\> asks\<T\>(Func\<TestBed.IO, T\> f) =\> new Subsys\<T\>((env, state) =\> (f(env), default, state, false)); public static Subsys\<TestBed.IO\> ask =\> new Subsys\<TestBed.IO\>((env, state) =\> (env, default, state, false)); public static Subsys\<TestBed.Person\> get =\> new Subsys\<TestBed.Person\>((env, state) =\> (state, default, state, false)); public static Subsys\<T\> gets\<T\>(Func\<TestBed.Person, T\> f) =\> new Subsys\<T\>((env, state) =\> (f(state), default, state, false)); public static Subsys\<LanguageExt.Unit\> put(TestBed.Person value) =\> new Subsys\<LanguageExt.Unit\>((env, state) =\> (default, default, value, false)); public static Subsys\<LanguageExt.Unit\> modify\<T\>(Func\<TestBed.Person, TestBed.Person\> f) =\> new Subsys\<LanguageExt.Unit\>((env, state) =\> (default, default, f(state), false)); public static Subsys\<T\> local\<T\>(Subsys\<T\> ma, Func\<TestBed.IO, TestBed.IO\> f) =\> ma.Local(f); public static Subsys\<T\> Pass\<T\>(this Subsys\<(T, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\>)\> ma) =\> new Subsys\<T\>(ma.\_\_comp.Pass()); public static Subsys\<T\> pass\<T\>(Subsys\<(T, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\>)\> ma) =\> new Subsys\<T\>(ma.\_\_comp.Pass()); public static Subsys\<(T, U)\> listen\<T, U\>(Subsys\<T\> ma, Func\<LanguageExt.Seq\<string\>, U\> f) =\> ma.Listen(f); public static Subsys\<T\> censor\<T\>(Subsys\<T\> ma, Func\<LanguageExt.Seq\<string\>, LanguageExt.Seq\<string\>\> f) =\> ma.Censor(f); public static Subsys\<LanguageExt.Unit\> tell(LanguageExt.Seq\<string\> what) =\> new Subsys\<LanguageExt.Unit\>(tell\<LanguageExt.ClassInstances.MSeq\<string\>, TestBed.IO, LanguageExt.Seq\<string\>, TestBed.Person, LanguageExt.Unit\>(what)); public static Subsys\<LanguageExt.Seq\<string\>\> ReadAllLines(string fileName) =\> ask.Map(\_\_env =\> \_\_env.ReadAllLines(fileName)); public static Subsys\<LanguageExt.Unit\> WriteAllLines(string fileName, LanguageExt.Seq\<string\> lines) =\> ask.Map(\_\_env =\> \_\_env.WriteAllLines(fileName, lines)); public static Subsys\<int\> Zero =\> ask.Map(\_\_env =\> \_\_env.Zero); public static Subsys\<string\> Name =\> get.Map(\_\_env =\> \_\_env.Name); public static Subsys\<string\> Surname =\> get.Map(\_\_env =\> \_\_env.Surname); }
    

    You'll notice toward the end that the members of IO and Person are available to use directly. So, you can write:

    using Subsys; var fullName = from n in Namefrom s in Surnameselect $"{n} {s}";
    

    It's possible to pick'n'choose which features of the RWS you want to use by putting in Unit or MUnit for the types. This is a State and Reader monad with no useful Writer functionality:

     [RWS(WriterMonoid: typeof(MUnit), Env: typeof(IO), State: typeof(Person))] public partial struct Subsys\<T\> {}
    

    As, with the Reader code-gen, it's possible to modify the constructor and failure functions:

     [RWS(WriterMonoid: typeof(MSeq\<string\>), Env: typeof(IO), State: typeof(Person), Constructor: "Pure", Fail: "Error")] public partial struct Subsys\<T\> {}