question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Filter/Map Either's Right to a Left

See original GitHub issue

Say I have the following method:

    public async Task<Either<Exception,int>> GetCurrentJobNumber()
    {
        // Build the job URI, which will then return the JSON.
        return (await _server.FetchJSON<JenkinsJob>(GetJobURIStem()))
            .Map(v => v.lastCompletedBuild)
            .Map(v => v.number);
    }

The FetchJSON will return an Either<Exception, JenkinsJob>. I’d like to then filter on lastCompletedBuild being null. However, if it is null, then I want to convert the Either to be a Right (new Exception(“No job has been run”)).

Is there a way to accomplish this?

Many thanks for the library! I’m just starting to try to use it. it is quite viral, but it turns functions like above from 10 lines of code into 3 very clear ones.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
louthycommented, Jul 1, 2016

@gordonwatts It’s a slightly awkward one, but a general rule-of-thumb if you want to change the monad itself rather than the value stored within it, then use Bind:

So where Map has the signature Func<T, R>; Bind has the signature Func<T, M<R>> where M is the monad.

With your example you’re switching from Right to Left and therefore working on the monad, rather than just mapping the values within.

This is an example with Bind

    public static async Task<Either<Exception, int>> GetCurrentJobNumber() =>
        (await _server.FetchJSON(GetJobURIStem()))
            .Bind(job => job.lastBuild == null
                ? Left<Exception, int>(new ApplicationException("No build completed"))
                : Right<Exception, int>(job.lastBuild.number));

It’s still not super pretty because Either requires two generic type params and therefore the type-inference is non-existent. What I’ll often do when using Either is to create some static helper functions for creating the Right and Left states for the common <L,R> pairs.

For example with a couple of functions (and using static ...):

        static Either<Exception, int> Success(int value) =>
            Right<Exception, int>(value);

        static Either<Exception, int> AppError(string msg) =>
            Left<Exception, int>(new ApplicationException(msg));

The code becomes much more declarative:

    public static async Task<Either<Exception, int>> GetCurrentJobNumber() =>
        (await FetchJSON(GetJobURIStem()))
            .Bind(job => job.lastBuild == null
                ? AppError("No build completed")
                : Success(job.lastBuild.number));

With cunning use of extension methods targeted at specific types (in this case JenkinsJob):

    static Either<Exception, int> EnsureCompletedBuild(this JenkinsJob self) =>
        self.lastBuild == null
            ? AppError("No build completed")
            : Success(self.lastBuild.number);

Then you have a reusable function that is super clear:

    public static async Task<Either<Exception, int>> GetCurrentJobNumber() =>
        (await FetchJSON(GetJobURIStem()))
            .Bind(EnsureCompletedBuild);

You could go even further and use the NewType system to give meaning to your int (and make it type-safe):

    public class JobId : NewType<int> 
    { 
        public JobId(int id) : base(id) { } 
    }

And update the helper functions to use it:

    static Either<Exception, JobId> EnsureCompletedBuild(this JenkinsJob self) =>
        self.lastBuild == null
            ? AppError("No build completed")
            : Success(new JobId(self.lastBuild.number));

    static Either<Exception, JobId> Success(JenkinsBuildId value) =>
        Right<Exception, JobId>(value);

    static Either<Exception, JobId> AppError(string msg) =>
        Left<Exception, JobId>(new ApplicationException(msg));

Then your resulting function looks like this:

    public static async Task<Either<Exception, JobId>> GetCurrentJobNumber() =>
        (await FetchJSON(GetJobURIStem()))
            .Bind(EnsureCompletedBuild);

The final result is a function that declares its intentions entirely.

Hope that helps! 😃

0reactions
TysonMNcommented, Dec 4, 2020

wait… .Value() I didn’t even know that was possible! I had been thinking man it kinda seems impossible to map some of these to other interfaces without direct access. Well you’ve ruined me now!

Actually, I don’t see Option<>.Value in language-ext. I was either getting confused between F#'s Option<>.Value or a custom extension method like the following that someone could write for Option<> in language-ext.

public static A Value<A>(this Option<A> ma) => ma.IfNone(() => throw new ArgumentException());

Either way, I have still seen people write (bad) code like that even if it wasn’t exactly C# and vanilla language-ext.

For context, I had an IEnumerable<Validation<Error, T>> and the only way I could reason about getting the valid ones was to do a where IsSuccess and then select match(t => t, err => throw). I couldn’t find an extension to get valid ones if one exists.

Here is how you get the valid ones.

IEnumerable<A> validOnes = sva.Map(va => va.ToOption()).Somes()

What is the main use or purpose of Apply? In your example it looks like the place where side effects are introduced. But I have also ended up using it like this to start a chain. unit.Apply(_ => GetEitherAsync()).... Which I guess is kinda side effect related since it is from a unit.

In C# (and my other programming languages), the syntax for function application uses a prefix notation.

f(x)

The purpose of Apply is to change function application (if you “squint”) to a postfix notation.

x.Apply(f)

I wrote about this in this post on my old blog. (I need to port that new my new blog.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spark filter + map vs flatMap - scala
isLeft).map(case Left(a) => a) val rddB = rddEither.filter(_.isRight).map(case Right(b) => b). flatMap val rddA = rddEither.
Read more >
filterMap - javadoc.io
filterMap. common. fun <K, A, B> Map<K, A>.filterMap(f: (A) -> B?): Map<K, B>. Content copied to clipboard. © 2022 CopyrightGenerated by dokka.
Read more >
Introduction to Vavr's Either
An Either is either a Left or a Right. By convention, the Left signifies a failure case result and the Right signifies a...
Read more >
FilterMap in rayon::iter - Rust
FilterMap ` creates an iterator that uses `filter_op` to both filter and map ... Either::Left items go into the first container, and Either::Right...
Read more >
FilterMap in rayon::iter - Rust
FilterMap ` creates an iterator that uses `filter_op` to both filter and map ... Either::Left items go into the first container, and Either::Right...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found