Skip to content

Instantly share code, notes, and snippets.

@hasparus
Last active August 15, 2022 01:30
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hasparus/ba5c31f391310a73026fc5f0383f6266 to your computer and use it in GitHub Desktop.
Save hasparus/ba5c31f391310a73026fc5f0383f6266 to your computer and use it in GitHub Desktop.
lightweight sum type matching in typescript
interface A { type: 'A', a: 10 };
interface B { type: 'B', b: [11] };
type AB = A | B;
type TypeOf<T extends { type: any }> = T extends { type: infer Type } ? Type : never;
type Cases<T extends { type: any }, R> = {
[P in TypeOf<T>]: (val: Extract<T, { type: P }>) => R
}
function match<T extends { type: string }, C extends Cases<T, any>>(
value: T,
cases: C
): ReturnType<C[keyof C]> {
return cases[value.type as TypeOf<T>](value as Extract<T, { type: string }>);
}
const ab: AB = { type: 'A', a: 10 } as any;
const _: 10 | 11 = match(ab, {
A: a => a.a,
B: b => b.b[0]
})
@hasparus
Copy link
Author

hasparus commented Dec 7, 2019

type Key = string | number;

type Sigil<K extends Key, T extends string = string> = {
  [_ in K]: T;
};

type TypeOf<K extends Key, T extends Sigil<K>> = T[K];

type Cases<K extends Key, T extends Record<K, any>, R> = {
  [P in TypeOf<K, T>]: (val: Extract<T, Record<K, P>>) => R;
};

function match<K extends Key>(key: K) {
  return function match1<T extends Sigil<K>, C extends Cases<K, T, any>>(
    value: T,
    cases: C
  ): ReturnType<C[keyof C]> {
    return cases[value[key]](value as Extract<T, Record<K, string>>);
  };
}

const matchType = match("type");

// test

interface A {
  type: "A";
  a: 10;
}
interface B {
  type: "B";
  b: [11];
}
type AB = A | B;

const ab: AB = { type: "A", a: 10 } as any;

// const _: 10 | 11
const _ = matchType(ab, {
  A: a => a.a,
  B: b => b.b[0]
});

const matchKind = match('kind');

interface Dog {
    kind: 'Dog',
    name: string;
    bark(): string;
}

interface Cat {
    kind: 'Cat';
    name: string;
    meow(): string;
}

const pet: Dog | Cat = { kind: 'Cat', name: 'Fluffy', meow: () => 'Meow!' } as any;

const _1 = matchKind(pet, {
    Cat: cat => cat.meow(),
    Dog: dog => dog.bark()
})

http://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=2&pc=1#code/C4TwDgpgBA0hJQLxQM7AE4EsB2BzKAPlNgK4C2ARhOgNwBQdokUAyprpgDYA8MUEAD2ARsAExSx4AGigAVfkJHjUGHPmRoseAHxIoAbzpQoAbQD6UHLAC6ALjn0AvvUbhostwHkAZrwXCxCTgQGXlBAOU2Dh4YbV1kWRMYaxcmaABhAEMUCBQ-cKUg6Tl-QqgAJQgAYwB7dFFeGUzsEG0ZcviDI1MABUtsOS9fGFDtOygACgA3TM57AFEhdEyq4G5Zduq6hpGoHriASiRdcqcXbxJsVcwagbJM4CqAC3zFQMlWiYBreHsYI8MxnQEGAJHQAwuV2ANzuD2eAEZ1qV3lEuLw2lB0sjlFkcnldhsoM1WtoJt1jDNOCQIPYNuSoFVsrl7Olugd7JVQeCPJBuOkTD8QDVvJjrLpAcYoMCuQNGXiTJTqQL4NZrNNZtSiRJFhgVmtCZVavVGiotLhDvRjM46I4GLVsGgoPdHk8edBkM7nhMAERpb0HFwAekDUGEaAYOGE6G8K2gAEEusY0vZvXHvZaifZ4QAGJx0SPUGNVaAAIUTobcKZL6e6FHsJnh8JSNtczDjZeQCaIJZc9sdmTrUHben0FcgKbTTSz2agji1RJaQZDfeAUDM08IUEbdBXa70ntdbgmA5kErj9kyxyJADpMlJuiX7BQrxRrxQTNnrDaA3bbo6DzAOCiPucJPBMADkXxAeBP75tgUZFtAAAiNT4BKxhQWI9jgShuDgfekrEJkZA0qaagZsYFCZOgXwTOyZF4HmcEIbGmIPOWGFAdhWTAOBFFESR9iaOR9IkTUADudFCaojEtjuf6rpAwD2Lhm48SOUCYaI3EPPhAmkeBABiVLeN4IB6WJ4n2HRV7gQAshAEkAITgbO87Er2ClrvCIEuoBYgTEpp70jx1mMsARyILo4XXpZdEEZKuHWaIqGRboKW4G+1G0Qc350EAA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment