Skip to content

Instantly share code, notes, and snippets.

@futuremint
Created May 22, 2020 19:57
Show Gist options
  • Save futuremint/c42668bf11b6de7afe5de97fc61726ea to your computer and use it in GitHub Desktop.
Save futuremint/c42668bf11b6de7afe5de97fc61726ea to your computer and use it in GitHub Desktop.
FizzBuzz implemented in Typescript's type system
// FizzBuzz with Typescript Types
// from https://gal.hagever.com/posts/typing-the-technical-interview-in-typescript/
// B is a subset, or equal to A
type Eq<A, B extends A> = 'passes';
// test it out
type test_eq = [
Eq<'Hello', 'Hello'>,
// Eq<'Hello', 'world'> // <- type error
];
// Have to make our own numeric types in order to do numeric operations
type NAN = 'invalid number'; // for negatives
type _0 = 0; // a type literal...
// type Increment<N> = [N, '+1']; // Ex: 1 = [1, '+1']
// type Decrement<N> = N extends Increment<infer D> // If N is a number that was incremented....
// ? D // then its the number that was incremented...
// : NAN; // otherwise its NAN because _0 wasn't incremented, and -1 is not in range
// A hack to implement increment/decrement within a limited range
type Increment<N> = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20][Extract<N, number>];
type Decrement<N> = [NAN,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20][Extract<N, number>];
// Our numbers (kinda annoying)
type _1 = Increment<_0>;
type _2 = Increment<_1>;
type _3 = Increment<_2>;
type _4 = Increment<_3>;
type _5 = Increment<_4>;
type _6 = Increment<_5>;
type _7 = Increment<_6>;
type _8 = Increment<_7>;
type _9 = Increment<_8>;
type _10 = Increment<_9>;
type _11 = Increment<_10>;
type _12 = Increment<_11>;
type _13 = Increment<_12>;
type _14 = Increment<_13>;
type _15 = Increment<_14>;
type _16 = Increment<_15>;
type _17 = Increment<_16>;
type _18 = Increment<_17>;
type _19 = Increment<_18>;
type _20 = Increment<_19>;
type test_decrement = [
Eq<Decrement<_1>, _0>,
Eq<Decrement<Decrement<_1>>, NAN>,
Eq<Decrement<Decrement<_2>>, _0>,
Eq<Decrement<Decrement<_20>>, _18>
];
// There aren't loops, so hack around limitations to make a recursive type
// type Subtract<N, Amount> = Amount extends _0
// ? N
// : Subtract<Decrement<N>, Decrement<Amount>>;
// hack around explicit recursion to return a key from object of stop conditionals
type Subtract<N, Amount> = {
amount_is_zero: N;
recurse: Subtract<Decrement<N>, Decrement<Amount>>;
}[Amount extends _0 ? 'amount_is_zero' : 'recurse'];
type test_sub = [
Eq<Subtract<_2, _1>, _1>,
Eq<Subtract<_2, _2>, _0>,
Eq<Subtract<_2, _0>, _2>,
Eq<Subtract<_6, _3>, _3>,
Eq<Subtract<_1, _2>, NAN>
];
// Implement divisibility using algorithm to subtract number from another
// until 0 (or NAN if less than 0)
type IsDivisibleBy<A, B> = {
a_eq_0: true;
a_lt_0: false;
recurse: IsDivisibleBy<Subtract<A, B>, B>;
}[A extends NAN ? 'a_lt_0' : A extends _0 ? 'a_eq_0' : 'recurse'];
type test_divisible_by = [
Eq<IsDivisibleBy<_4, _2>, true>,
Eq<IsDivisibleBy<_3, _2>, false>,
Eq<IsDivisibleBy<_5, _3>, false>,
Eq<IsDivisibleBy<_6, _3>, true>
]
// Now for FizzBuzz!
type IsDivisibleBy3<N> = IsDivisibleBy<N, _3>;
type IsDivisibleBy5<N> = IsDivisibleBy<N, _5>;
// make an And type so we can reuse the above:
type And<A, B> = A extends true ? (B extends true ? true : false) : false;
type IsDivisibleBy15<N> = And<IsDivisibleBy3<N>, IsDivisibleBy5<N>>;
// Now we can make FizzBuzz
type FizzBuzzN<N> = IsDivisibleBy15<N> extends true
? 'FizzBuzz'
: IsDivisibleBy3<N> extends true
? 'Fizz'
: IsDivisibleBy5<N> extends true
? 'Buzz'
: N;
type text_fizzbuzzn = [
Eq<FizzBuzzN<_1>, _1>,
Eq<FizzBuzzN<_3>, 'Fizz'>,
Eq<FizzBuzzN<_4>, _4>,
Eq<FizzBuzzN<_5>, 'Buzz'>,
Eq<FizzBuzzN<_6>, 'Fizz'>
];
// Output stuff nicely
// adds an element to an array, by hack to steal type of an inline fuction
type Unshift<Element, List extends Array<any>> = Parameters<
(e: Element, ...list: List) => any
>;
type test_unshift = [
Eq<Unshift<1, []>, [1]>, // empty array
Eq<Unshift<2, [1]>, [2, 1]>, // front of array
Eq<Unshift<'hello', [2, 1]>, ['hello', 2, 1]> // mixed types
];
// Contructs a list of results of FizzBuzz up to a number
type FizzBuzzUpTo<N, Output extends any[] = []> = {
output: Output;
recurse: FizzBuzzUpTo<Decrement<N>, Unshift<FizzBuzzN<N>, Output>>;
}[N extends _0 ? 'output' : 'recurse'];
type test_fizzbuzzupto = [
Eq<
FizzBuzzUpTo<_20>,
[_1, _2, 'Fizz', _4, 'Buzz', 'Fizz', _7, _8, 'Fizz', 'Buzz',
_11, 'Fizz', _13, _14, 'FizzBuzz', _16, _17, 'Fizz', _19, 'Buzz']
>
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment