Skip to content

Language-Ext 5.0 alpha-3

Pre-release
Pre-release
Compare
Choose a tag to compare
@louthy louthy released this 23 Mar 11:13
· 31 commits to v5-transducers since this release

WARNING: THIS IS AN ALPHA RELEASE AND SHOULD BE CONSUMED WITH CARE! NOT FOR PRODUCTION.

Bug fixing and TODO resolving release, with some minor featurettes!

For those that don't know yet (and there's no reason to think you should, because I haven't announced it yet) -- the Pipes Effect system now has the ability to lift any monad into its stack (previously it only allowed Aff to be lifted). It is now a general monad transformer like ReaderT, OptionT, EitherT, etc.

As, with all monad-transfomers, when you 'run' the transformer, it generates the lifted monad. You can think of this being like a mini-compiler that takes the monad stack and compiles down to the inner-most monad, which can then just be run as normal.

The problem for Pipes is that there's usually lots of recursion, repetition (using repeat, retry), or iteration (using yieldAll, etc.). This is problematic when you don't know anything about the inner monad. The transformer can't run the inner monad, because it only has access to the Monad interface (Bind) and the inherited interfaces of Applicative and Functor (Apply, Action, Map, and Pure). So, doing iteration requires recursion, and recursion blows the stack in C#.

Previously Pipes were able to directly Run the Aff because the Pipe system knew it was working only with Aff. This allowed it to flatten the recursion.

Anyway, now Pipes has internal support for any Foldable. That means yieldAll(...) can take a sequence from any foldable (Range, EnumerableM, HashMap, HashSet, Lst, Map, Seq, Either, Option, Validation, Identity, ... and any you write) and yield the values within the structure through the pipe. Functions like repeat(ma) - which continually repeat an operation until it fails - have also been implemented internally as something that iterates over an infinite foldable.

This functionality has been enabled by adding a new method to the Applicative trait: Actions. You might know the existing Action(K<M, A> ma, K<M, B> mb) method that runs the first applicative (ma), ignores its result, and then runs the second applicative mb, returning its result.

Actions instead takes an IEnumerable<K<M, A>>:

K<F, A> Actions<A>(IEnumerable<K<F, A>> fas)

It runs each applicative action and ignores its result, returning the result of the last item. That means a sequence of Proxy values (Proxy is the monad-transformer for pipes) can be mapped - the map will just run (using RunEffect) the Proxy - producing a sequence of whatever the lifted inner-monad is for the Proxy. This lazy sequence of monads can then be invoked by calling Actions on it, which will lazily walk the sequence, evaluating the inner-monad one-by-one.

There is a default implementation, but it has the same lack of knowledge that Pipes had, so it should be overridden for computation based applicatives (that usually need invoking with without an argument). Here's the override for Eff<RT, A>:

static K<Eff<RT>, A> Applicative<Eff<RT>>.Actions<A>(IEnumerable<K<Eff<RT>, A>> fas) =>
    from s in getState<A>()
    from r in Eff<RT, A>.Lift(
        rt =>
        {
            Fin<A> rs = Errors.SequenceEmpty;
            foreach (var kfa in fas)
            {
                var fa = kfa.As();
                rs = fa.Run(rt, s.Resources, s.EnvIO);
                if (rs.IsFail) return rs;
            }
            return rs;
        })
    select r;

You can see how:

  • It's able to gather information, like the runtime, resources, and IO environment.
  • It knows how to run itself, whereas the generic transformer can't.
  • It can shortcut the operation when any effect fails.

And so, if you want to use your own monads with Pipes then you should implement Actions.

There's still more to do with Pipes, but all of the examples in EffectsExamples now work, which is a good sign!

WARNING: THIS IS AN ALPHA RELEASE AND SHOULD BE CONSUMED WITH CARE! NOT FOR PRODUCTION.