LoginSignup
5
0

More than 3 years have passed since last update.

TypeScript で型レベル FizzBuzz

Last updated at Posted at 2019-11-19

動機と目的

TypeScript 初心者なので、FizzBuzz を書こうと思いました。
TypeScript にはリテラル型とタプル型があるので、[1, 2, "Fizz", 4, "Buzz", ...] という FizzBuzz 型を生成することができるはずです。
型のインスタンス化の制限のため、あまり長くは生成できそうにありませんが、2 回目の "FizzBuzz" くらいまではたどり着きたいです。
外部ライブラリは使用しません。

結果

今の私の TypeScript 力で、FizzBuzz 感を出しつつ 2 回目の "FizzBuzz" まで生成しようとすると、こんな感じになりました。

fizzbuzz.ts
type Buzz<N, ZS1 extends readonly any[] = [0]> =
  {
  0:
    []
  1:
    ((z1: 0, ...zs1: ZS1) => any) extends ((...zs2: infer ZS2) => any) ?
    ((z2: 0, ...zs2: ZS2) => any) extends ((...zs3: infer ZS3) => any) ?
    ((z3: 0, ...zs3: ZS3) => any) extends ((...zs4: infer ZS4) => any) ?
    ((z4: 0, z5: 0, ...zs4: ZS4) => any) extends ((...zs6: infer ZS6) => any) ?
    Buzz<N, ZS6> extends infer BS6 ?
    BS6 extends readonly any[] ?
    ((b1: ZS1['length'], b2: ZS2['length'], b3: ZS3['length'], b4: ZS4['length'], b5: 'Buzz', ...bs6: BS6) => any) extends ((...bs1: infer BS1) => any) ?
    BS1 :
    never : never : never : never : never : never : never;
  }[ZS1['length'] extends N ? 0 : 1];

type Fizz<BS1 extends readonly any[]> =
  {
  0:
    [];
  1:
    ((...bs1: BS1) => any) extends ((b1: infer B1, b2: infer B2, b3: infer B3, ...bs4: infer BS4) => any) ?
    Fizz<BS4> extends infer FS4 ?
    FS4 extends readonly any[] ?
    ((f1: B1, f2: B2, f3: B3 extends 'Buzz' ? 'FizzBuzz' : 'Fizz', ...fs4: FS4) => any) extends ((...fs1: infer FS1) => any) ?
    FS1 :
    never : never : never : never;
  }[BS1['length'] extends 0 ? 0 : 1];

// 'N' shall be of the form '15K + 1'.
type FizzBuzz<N> = Fizz<Buzz<N>>;

const fizzBuzz: FizzBuzz<31> = 'FizzBuzz';

typescript@3.7.2実行しました。

% tsc fizzbuzz.ts
fizzbuzz.ts:34:7 - error TS2322: Type '"FizzBuzz"' is not assignable to type '[1
, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzB
uzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28
, 29, "FizzBuzz"]'.

33 const fizzBuzz: FizzBuzz<31> = 'FizzBuzz';
         ~~~~~~~~


Found 1 error.

できていそうです。

解説

TypeScript のままだと読みにくいので、TypeScript に書き直します。

function buzz(n: number, zs1: readonly 0[] = [0]): readonly ('Buzz' | number)[] {
  if (zs1.length === n) {
    return [];
  } else {
    const zs2 = [0, ...zs1] as const;
    const zs3 = [0, ...zs2] as const;
    const zs4 = [0, ...zs3] as const;
    const zs6 = [0, 0, ...zs4] as const;
    const bs6 = buzz(n, zs6);
    const bs1 = [zs1.length, zs2.length, zs3.length, zs4.length, 'Buzz', ...bs6] as const;
    return bs1;
  }
}

function fizz(bs1: readonly ('Buzz' | number)[]): readonly ('FizzBuzz' | 'Fizz' | 'Buzz' | number)[] {
  if (bs1.length === 0) {
    return [];
  } else {
    const [b1, b2, b3, ...bs4] = bs1;
    const fs4 = fizz(bs4);
    const fs1 = [b1, b2, b3 === 'Buzz' ? 'FizzBuzz' : 'Fizz', ...fs4] as const;
    return fs1;
  }
}

// 'n' shall be of the form '15k + 1'.
function fizzBuzz(n: number): readonly ('FizzBuzz' | 'Fizz' | 'Buzz' | number)[] {
  return fizz(buzz(n));
}

console.log(fizzBuzz(31));

自然数列を生成する方法が肝のように思います。
自然数から直接次の自然数を得ることはできないようなので、今回は適当な配列を伸ばしていき、その length を取ることで自然数列を作っています。

また 5 ずつまとめて自然数を生成することで、型のインスタンス化の制限を避けようとしています。ついでに "Buzz" も埋め込んでいます。
もっと言えば 15 ずつ処理することもできるはずですが、個人的に FizzBuzz してる感が薄れるので、やっていません。

まとめ

TypeScript で FizzBuzz をやってみました。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0