Created
May 22, 2020 19:57
-
-
Save futuremint/c42668bf11b6de7afe5de97fc61726ea to your computer and use it in GitHub Desktop.
FizzBuzz implemented in Typescript's type system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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