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 aSeq<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 theReader
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 evenS
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 anS
state that can vary (by callingput(x)
). Below, is a version that doesn't have a variableS
generic parameter, and instead has a fixed state embedded in the wrappedRWS
monad. So,put
can only be called with anotherPerson
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
andPerson
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 inUnit
orMUnit
for the types. This is aState
andReader
monad with no usefulWriter
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\> {}