Enforce a null check with composable code branching using Either

Brian Lonsdorf
InstructorBrian Lonsdorf
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 2 years ago

We define the Either type and see how it works. Then try it out to enforce a null check and branch our code.

[00:00] Now we're going to discuss a new type, and we call this type either. Either is defined as a right or a left. These are two sub-types, or sub-classes if you will, of either, and either doesn't actually come into play. It will just refer to one of these two types.

[00:15] Let's go ahead and define these types. We'll define right, comment this out here. Right is almost exactly like our definition of box here. We have a map which will take an F, and run F on X, and we'll box it back up in our right here. Then we'll do our little inspector trick so we can see what we're looking at when we're in our console.

[00:36] I should replace these with these. There we are. Now, if we say our result is a right of three, and we map over that three, maybe add one to it. We console.log this. We should have exactly like our box, right of four. If we go ahead and map and map and map, it will pass our value through just like composition. We've already seen this with box. Now we should have a right of two. There we are.

[01:02] The difference from box will reveal itself when we define our fold. Let's start by defining our left, though. We make our left type, the other part in either. Now we have our right and we have our left. We'll define exactly the same information, except left is a stubborn little bugger. It will not run the F on the X. It will ignore the F altogether.

[01:22] When we take a look at this, our right added one and divided by two. If we put this inside a left instead, we will keep our left of three untouched. Left refuses to run any of our requests here. Why is this useful? To get at why this is useful, we will define our fold here and look at some real use cases.

[01:43] Fold, if you remember from box, removes our value from the type when we run our function. It will drop out of our container type and give us our value back. However, we have two types here. We have a right or a left. Usually, we don't know if we have a right or a left. We don't know which one it is. That's a bit funny to have the right on the left side and left on the right side. Never mind.

[02:04] If our fold is going to handle either of these cases, it needs to take two functions. If it is the right case, we will run the second function, G here. If it's the left case, we will run the first function. This allows us to branch our code and run the right or left side. Let's go ahead and put this back in a right. We'll say a right of two, and at the end here we will fold it.

[02:28] If it is a left, we'll say error. If it is a right, we'll return it back. Let's look at this. We should have our result of 1.5. We added 1 to 2 and divided by 2. Now, if we change it to a left, it will ignore all our requests and dump out an error here.

[02:46] This allows us to do some pure functional error handling, code branching, null checks, and all sorts of concepts that capture disjunction, or the concept of or. That's why we call this either, it's either/or. We can use that all throughout our code.

[03:01] Let's start with another example here of finding a color. We have this find color function. Find color will take a name, look up this hash, and tell us the hex value for that color. Let's use it here. We'll say find color red. If we look at the results, we will get the hex value for red.

[03:19] Now, suppose we want to slice this little bit off, this little hash off here, and then say to upper case, this will be our final result. It will be the upper cased FF4444.

[03:31] What if this function, though, what if we don't have the color? What if we pass in green? What's going to happen is it's going to die. If we look up here we see, "Find color green.slice." It can't call slice of undefined. That's because we never returned any string back. How do we handle this?

[03:48] We can use either type here to say const found. If we find it -- found -- we'll return a right of it, otherwise we'll return a left of null, it doesn't really matter. Then we'll have to write a return here. Now our find color does not return a string, it returns an either.

[04:06] With this either type we can't just call slice on it, we'll have to map over it. Let's go ahead and map and take our color. We'll call color.slice, and then we can finish our call here with a fold. If it's an error, we'll return, "no color."

[04:21] Then, if it is there with a color, we'll call to uppercase on it, just like that. I like to indent these so they're even here, so the E and the C line up like that, but it's up to you.

[04:33] Let's run this, and we should get, "No color," because it couldn't find green. If we run it again with, say, blue, we'll get our upper cased blue just like that. What's happened here is find color actually tells us whether or not it's going to return a null right here in the signature.

[04:50] Whenever I call find color, I will get a right or a left back, and I must map over it. I can't just get blindsided by a null in run-time if I pass the wrong color. This is very good.

[04:59] At the time of programming, we know if we're going to get a null or not. We can map over it. If it's not found, the map doesn't get run because it is a left. If it is found, map will get run, and it runs this part of the fold instead of the left side.

[05:10] Now, find color has multiple expressions.

[05:13] We can take this a little step further here. This is very common because we want to return these values instead of nulls from here on out to make our code safer and more predictable. Now, we can say from nullable, and we'll take some X here and say if it is not equal to null, this captures the undefined case as well. We'll return a right of X. Otherwise, we'll return a left of null.

[05:35] We can rewrite this expression in one go by wrapping it in a from nullable. I have to move this on the other side there. There we are. We have the same results. If it finds it, it puts it into the right. If it doesn't, it goes into the left. This will still work. Let's give it a shot and there we are. If we pass in blues, we do not get the undefined problem. We get no color.

Vamshi
Vamshi
~ 8 years ago

I still don't understand how the inspect method works. Anyone please help? Thanks

Brian Lonsdorf
Brian Lonsdorfinstructor
~ 8 years ago

Hi!

Node will implicitly call inspect() on x when you console.log(x). Browsers do not support this so I often use .toString() and log with console.log(String(x))

Chapin
Chapin
~ 8 years ago

For those checking their work on browser you can use instead of node console, as per suggested above.

toString: () => `Right(${x})`
...
console.log(String(result))
Dillon
Dillon
~ 7 years ago

hi. you r a genius

Michael
Michael
~ 7 years ago

I've been trying to write a typescript version of Right:

export class Right<T> {
    public constructor(private x: T) { }

    public chain<T1>(f: (x: T) => Either<T1>): Either<T1> {
        return f(this.x);
    }

    public map<T1>(f: (x: T) => T1): Either<T1> {
        return new Right(f(this.x));
    }

    public fold<T1,T2>(f: (x: T) => T1, g: (x: T) => T2): T1|T2 {
        return g(this.x);
    }

    public inspect(): string {
        return `Right(${this.x})`;
    }
}

But I'm having problems with getting fold correct.

        const res = new Right(3)
            .map(x => x * 3)
            .fold(x => x, x1 => x1);

The compiler give the following error:

error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '(<T1, T2>(f: (x: number) => T1, g: (x: number) => T2) => T1 | T2) | ((f: (x: number) => number, g...' has no compatible call signatures.

Any ideas what I'm doing wrong?

Michael
Michael
~ 7 years ago

I've got it going now, here is the full implementation:

export type Either<T> = Right<T> | Left<T>;

export class Right<T> {
    public constructor(private x: T) { }

    public chain<T1>(f: (x: T) => Right<T1>): Right<T1> {
        return f(this.x);
    }

    public map<T1>(f: (x: T) => T1): Right<T1> {
        return new Right(f(this.x));
    }

    public fold<T1, T2>(f: (x: T) => T1, g: (x: T) => T2): T1 | T2 {
        return g(this.x);
    }

    public inspect(): string {
        return `Right(${this.x})`;
    }
}

export class Left<T> {
    public constructor(private x: T) { }

    public chain(f: (x: T) => Left<T>): Left<T> {
        return new Left(this.x);
    }

    public map(f: (x: T) => T): Left<T> {
        return new Left(this.x);
    }

    public fold<T1, T2>(f: (x: T) => T1, g: (x: T) => T2): T1 | T2 {
        return f(this.x);
    }

    public inspect(): string {
        return `Left(${this.x})`;
    }
}

function findColor(name: string): Either<string> {
    const colors: IColorIndexable = { red: '#ff4444', blue: '#3b5998', yellow: '#fff68f' };
    const found: string = <string>colors[name];
    return found
        ? new Right(found)
        : new Left(null);
}

interface IColorIndexable {
    [key: string]: string
}

console.log(findColor('green').fold<string, string>(x => 'error', x1 => x1.toUpperCase()))
console.log(findColor('yellow').fold<string, string>(x => 'error', x1 => x1.toUpperCase()))


Iain Maitland
Iain Maitland
~ 7 years ago

Small gotcha

const fromNullable = x =>
  x != null ? Right(x) : Left(null)

note: != is used not !==.

Oleg
Oleg
~ 7 years ago

I don't understand why you named function "fromNullable" and used

x != null

Actually findColor need verification for undefined, not null.

Guy
Guy
~ 6 years ago

I don't understand why you named function "fromNullable" and used

x != null

Actually findColor need verification for undefined, not null.

Hey @Oleg, the reason that statement works is because of type coercion. So, undefined is treated like null in this case. If you run undefined != null in a REPL, you will still get back false for that reason. If you add another = to the end, then we get some finer grained differentiation, and undefined is not treated the same as null.

mathias gheno
mathias gheno
~ 2 years ago

In the description transcript there is a code error. Right sould be different.

const Right = x =>
  ({
    map: f => Right(f(x)),
    fold: (f, g) => g(x),
    inspect: () => 'Right(${x})'
  })
Lucas Minter
Lucas Minter
~ 2 years ago

In the description transcript there is a code error. Right sould be different.

const Right = x =>
  ({
    map: f => Right(f(x)),
    fold: (f, g) => g(x),
    inspect: () => 'Right(${x})'
  })

You are correct! I got this issue fixed in the transcripts.

Markdown supported.
Become a member to join the discussionEnroll Today