A Result Type

Some functions can fail. Buying in a shop fails when the shop doesn't have the item, or when the hero can't afford it. A function could return undefined when it fails, but then the caller doesn't know why. Combine what you know, a discriminated union (lesson 6) and a type parameter, and you get a type for the result of anything that can fail:

type Result<T> = { ok: true; value: T } | { ok: false; error: string };

A Result<T> is either a success, with a value of type T, or a failure, with an error message. The tag is ok. Its types are the literal types true and false: like "potion", each allows only that one value. One type is enough for all functions that can fail: Result<number>, Result<Hero>, and so on.

type Result<T> = { ok: true; value: T } | { ok: false; error: string };

function shareGold(gold: number, heroes: number): Result<number> {
    if (heroes === 0) {
        return { ok: false, error: "nobody to share with" };
    }
    return { ok: true, value: gold / heroes };
}

const share = shareGold(120, 3);
if (share.ok) {
    console.log(`Each hero gets ${share.value} gold`);
} else {
    console.log(`Can't share: ${share.error}`);
}

This prints:

Each hero gets 40 gold

Inside if (share.ok), TypeScript knows it's a success, so share.value is a number. In the else branch, it's a failure, which has an error. Before the check, share could be either, so using value is an error. Add console.log(shareGold(120, 0).value); at the end, and TypeScript reports:

main.ts(16,31): error TS2339: Property 'value' does not exist on type 'Result<number>'.
  Property 'value' does not exist on type '{ ok: false; error: string; }'.

The second line explains it: a failure has no value. That's the point of Result: as with the undefined from find, TypeScript makes every caller deal with both cases, so a failure can't be forgotten. And unlike undefined, a failure also says what went wrong. Many TypeScript programs use this pattern.

Typical mistakes:

  • Mixing the two cases: return { ok: true, error: "…" }; gives Object literal may only specify known properties, and 'error' does not exist in type '{ ok: true; value: number; }'. (TS2353).
  • Writing ok: "true" (a text) instead of ok: true.
  • Leaving out the type argument: Result alone gives TS2314, Generic type 'Result' requires 1 type argument(s).

Instructions

Below the shop, declare the type Result<T> exactly as above: { ok: true; value: T } or { ok: false; error: string }.

Write a function buyItem(shop: Item[], name: string, gold: number): Result<Item>. It finds the item by its name with find. If the shop doesn't have it, it fails with the error `no ${name} in the shop`. If the item costs more than gold, it fails with `not enough gold for ${name}`. Otherwise it succeeds, with the item as the value.

Kira has 50 gold and tries to buy three things. Add this loop at the end of the program:

for (const name of ["Healing Potion", "Silver Sword", "Dragon Egg"]) {
    const result = buyItem(shop, name, 50);
    if (result.ok) {
        console.log(`Bought: ${result.value.name}`);
    } else {
        console.log(`Failed: ${result.error}`);
    }
}

The program prints:

Bought: Healing Potion
Failed: not enough gold for Silver Sword
Failed: no Dragon Egg in the shop

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

6/9