Code Monkey home page Code Monkey logo

Comments (4)

louthy avatar louthy commented on June 11, 2024 3

As noted above, I have removed EitherAsync, OptionAsync, TryAsync -- this library is now against async/await because of the unnecessary code-bloat it causes. It is also part of a bigger move to monad transformers.

There is now just one officially supported async type: IO<A> which is the IO monad. The IO monad is very lightweight and simple, but it can be composed with other transformer monads to augment its capability.

Also, see my blog for details on what's happening with v5.

The current set of transformer monads are:

  • ContT - continuations
  • IdentityT
  • EitherT
  • OptionT
  • TryT
  • ValidationT
  • ResourceT - resource tracking (use, release)
  • ReaderT
  • WriterT
  • StateT
  • Proxy - pipes base transformer

So, for the monadic types that were compositions of other types, they've mostly gone, but you can now use the transformers to get that exact functionality:

  • EitherAsync<L, R> is EitherT<L, IO, R>
  • OptionAsync<A> is OptionT<IO, A>
  • TryAsync<A> is TryT<IO, A>
  • TryOption<A> is TryT<Option, A>
  • TryOptionAsync<A> is TryT<OptionT<IO>, A> (three levels of monadic types stacked)

This also means the much requested (but never implemented) ValidationAsync can also be built:

    ValidationT<F, IO, A>

So, there's now no limitation to how you can stack your types.

You can use it like so:

var mx = EitherT<Error, IO, int>.Right(100);
var my = EitherT<Error, IO, int>.Right(100);
var mz = EitherT<Error, IO, int>.Left(Errors.Cancelled);

var mr = from x in mx
         from y in my
         from z in mz
         select x + y + z;

IO<Either<Error, int>> ior = mr.Run().As();   
Either<Error, int>     r   = ior.Run();

You'll notice that the Run section at the end is more involved. Each Run unwraps the outer monad.

You'll also notice that there's no awaiting of the result. There's no need for that now as the thread automatically yields (when waiting for an IO operation to complete) without the need to use the async/await machinery.

If you want to lift an asynchronous operation then you have a few options.

First, you need to construct an IO monad. This can be done with one of the following instructions:

  • IO.Pure(x) - lifts a pure value into the IO monad
  • IO.Fail(Error) - lifts a failure value into the IO monad
  • IO.lift(f) - where f is a synchronous function
  • IO.liftAsync(f) - where f` is an asynchronous function

The Prelude also has a liftIO with liftVIO variants for ValueTask functions. It also has some additional overloads that lift for specific monad transformers tasks, so if you have a Task<Either<L, R>> then calling liftIO with the task will get you a EitherT<L, IO, R>.

As well as the functions listed above -- which you'll want to use most of the time -- each transformer monad has a LiftIO static function you can call:

    /// <summary>
    /// Lifts a given monad into the transformer
    /// </summary>
    /// <param name="monad">Monad to lift</param>
    /// <returns>`EitherT`</returns>
    public static EitherT<L, M, A> LiftIO(IO<A> monad) =>
        Lift(M.LiftIO(monad));

    /// <summary>
    /// Lifts a given monad into the transformer
    /// </summary>
    /// <param name="monad">Monad to lift</param>
    /// <returns>`EitherT`</returns>
    public static EitherT<L, M, A> LiftIO(IO<Either<L, A>> monad) =>
        Lift(M.LiftIO(monad));

With the example above you can do this:

var mr = from x in mx
         from y in my
         from v in liftIO(e => System.IO.File.ReadAllTextAsync(path, e.Token))
         from z in mz
         select x + y + z;

The e variable is an EnvIO type that carries the CancellationToken, CancellationTokenSource, and SynchronizationContext.

If you look at the set of static functions available for the IO monad, you'll see all the extra functionality that is now available for any transformer stack that uses it. Including, cancellation, scheduled retries and repeats, local cancellation environments, thread forking and yielding. So, there's a ton more power than the original EitherAsync.

The one thing to note though: IO is designed to be very simple and lightweight. So, it throws exceptions if there's a failure. So, when you call the final Run to realise the Either<L, R>, it can throw:

IO<Either<Error, int>> ior = mr.Run().As();  // this is ok
Either<Error, int> r = ior.Run();            // this can throw

Obviously, because we don't know what the L is there's no way of constructing one from an Exception. So, you need your own error handling strategy for whatever is the Left value of your EitherT.

My suggestion is to wrap up the running logic into bespoke extension method for your types:

public static class YourErrorExtension
{
    public static Either<YourError, A> Run<A>(this EitherT<YourError, IO, A> ma)
    {
        try
        {
            return ma.Run().As().Run();
        }
        catch (Exception e)
        {
            return MapExceptionToYourError(e);
        }
    }
}

Another thing you can do is to wrap the transformer EitherT<L, IO, R> into a new type:

public record EitherAsync<L, R>(EitherT<L, IO, R> runEitherT)
{
    // ... add functions that map to `EitherT` and then rewrap in an EitherAsync
}

This approach isn't easy to do because you have to repeat the interface for EitherT. My plan is to make this easy through code-generation. If you want to see what this looks like you can take a look at the Eff monad; it is a wrapper around StateT<ResourceT<IO>>> .

So, yeah, v5 is the once in a decade change to this library that will hopefully set it up for the next decade. It's not free for existing code-bases, but it's much more powerful once you've migrated to it.

Also, note, I do not advise migrating to v5 whilst its still in an alpha or beta phase. Any migration is likely to be quite a bit of work and, if there are bugs, there may be times when I don't have the bandwidth to respond.

from language-ext.

aloslider avatar aloslider commented on June 11, 2024 1

I'm pretty sure it was removed, see this

from language-ext.

ruxo avatar ruxo commented on June 11, 2024

and I'm about to report a bug(?) in EitherAsync pipe operation... but I guess it's no longer needed 😔

image

from language-ext.

vksokolov avatar vksokolov commented on June 11, 2024

Got it, thank you!

from language-ext.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.