Enforce a null check with composable code branching using Either

InstructorBrian Lonsdorf

Share this video with your friends

Send Tweet

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

Vamshi
Vamshi
~ 4 years ago

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

Brian Lonsdorf
Brian Lonsdorf(instructor)
~ 4 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
~ 4 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
~ 4 years ago

hi. you r a genius

Michael
Michael
~ 3 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
~ 3 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
~ 3 years ago

Small gotcha

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

note: != is used not !==.

Oleg
Oleg
~ 3 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
~ 3 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.