FizzBuzz型:TypeScriptの型だけでFizzBuzzしてみた

に公開

お題の通りすぎて恥ずかしいですが、TypeScriptの型機構のみを使用してFizzBuzzもどきを作ってみました。
見ていただけるとわかると思いますが、本当にもどきです。
私のプログラムだと15までしかFizzBuzzの処理が行われないのでそれ以降は考慮していません。
ね?もどきでしょ?

ただ、Tsの型機構で遊ぶにはちょうど良いかなと。。。
お許しください。。
それでは全く実用性のないコードをご覧くださいm(__)m

ソースコード

type IsDivisibleBy3<N extends number> = 
  N extends 3 | 6 | 9 | 12 | 15 ? true : false;

type IsDivisibleBy5<N extends number> = 
  N extends 5 | 10 | 15 ? true : false;

type IsDivisibleBy15<N extends number> = 
  N extends 15 ? true : false;

type FizzBuzz<N extends number> =
  IsDivisibleBy15<N> extends true ? "FizzBuzz" :
  IsDivisibleBy3<N> extends true ? "Fizz" :
  IsDivisibleBy5<N> extends true ? "Buzz" : N;

type Counter = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
type NextNumber<N extends Counter> = 
  N extends 1 ? 2 :
  N extends 2 ? 3 :
  N extends 3 ? 4 :
  N extends 4 ? 5 :
  N extends 5 ? 6 :
  N extends 6 ? 7 :
  N extends 7 ? 8 :
  N extends 8 ? 9 :
  N extends 9 ? 10 :
  N extends 10 ? 11 :
  N extends 11 ? 12 :
  N extends 12 ? 13 :
  N extends 13 ? 14 :
  N extends 14 ? 15 :
  never;
type FizzBuzzLoop<
  Current extends Counter = 1,
  Acc extends any[] = []
> = Current extends 15
  ? [...Acc, FizzBuzz<15>]  // 終了条件
  : FizzBuzzLoop<NextNumber<Current>, [...Acc, FizzBuzz<Current>]>;

// 結果
type result = FizzBuzzLoop;

結果

type result = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"]

FizzBuzz型できました!(15までだけど...)

はぁー、固定値で型定義しないと実装できなかったり、15までしか対応できなかったり、
自分の乏しい技術力を呪います。。

解説

IsDivisibleBy3/5/15

type IsDivisibleBy3<N extends number> = 
  N extends 3 | 6 | 9 | 12 | 15 ? true : false;

type IsDivisibleBy5<N extends number> = 
  N extends 5 | 10 | 15 ? true : false;

type IsDivisibleBy15<N extends number> = 
  N extends 15 ? true : false;

IsDivisibleByX(X=3 | 5 | 15)型はジェネリクスで数値を受け取り、その数値がXで割れる数字かどうかを判定する型です。(15までの範囲)
割れる場合はtrueのリテラル型
割れない場合はfalseのリテラル型になります。
見ての通り、実際に除算をしているわけではなく固定値で数値を設定し、
extendsできるかどうかで判定しています。
※割り算処理が型機構でできるのかどうかわかりませんが、工夫してできるならそれで置換したい...

FizzBuzz

type FizzBuzz<N extends number> =
  IsDivisibleBy15<N> extends true ? "FizzBuzz" :
  IsDivisibleBy3<N> extends true ? "Fizz" :
  IsDivisibleBy5<N> extends true ? "Buzz" : N;

FizzBuzz型はジェネリクスにて数値を受け取り、
3項演算子で先ほどのIsDivisibleByX型をつなげて15⇒3⇒5と割れるかどうかを確認します。
trueのリテラル型が返ってきたら、文字列をfalseリテラルの場合はそのままその文字を返却します。

FizzBuzzLoop

type Counter = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
type NextNumber<N extends Counter> = 
  N extends 1 ? 2 :
  N extends 2 ? 3 :
  N extends 3 ? 4 :
  N extends 4 ? 5 :
  N extends 5 ? 6 :
  N extends 6 ? 7 :
  N extends 7 ? 8 :
  N extends 8 ? 9 :
  N extends 9 ? 10 :
  N extends 10 ? 11 :
  N extends 11 ? 12 :
  N extends 12 ? 13 :
  N extends 13 ? 14 :
  N extends 14 ? 15 :
  never;
type FizzBuzzLoop<
  Current extends Counter = 1,
  Acc extends any[] = []
> = Current extends 15
  ? [...Acc, FizzBuzz<15>]  // 終了条件
  : FizzBuzzLoop<NextNumber<Current>, [...Acc, FizzBuzz<Current>]>;

FizzBuzzLoops型はCounter型として定義されている数値をもとに、再帰的処理を実施。
Currentジェネリクスの値が15になると再帰処理を抜けて処理がすべて終了します。

FizzBuzzLoop<NextNumber<Current>, [...Acc, FizzBuzz<Current>]>

再帰処理に部分についてですが、
CurrentジェネリクスにはNextNumber<Current>を渡しています。
NextNumber<N extends Counter>はジェネリクスにて渡されたCounter型を満たした数値をもとに、
特定の数値の場合は特定の数値リテラル型を返す型となっています。
※取得元の数値と返却数値は+1の関係のため、FizzBuzzLoop型のCurrentに渡る数値が再帰するごとに+1され、15に届いたら再帰が終わるという流れです

Accジェネリクスには[...Acc, FizzBuzz<Current>]を渡しています。
Accを展開し、最後尾にFizzBuzz型から返却される文字 or 数値リテラル型を追加することで、
再帰処理を進むごとに以下のようにAccの中に要素が追加されていきます。

[1]
[1,2]
[1,2,"Fizz"]

結び

超絶技巧プログラミングとは、「実用的なプログラミング言語を使って全く実用的ではないものを作る遊び」
・・・中略・・・
超絶技巧プログラミングで作られるプログラムは必然的に何の役にも立ちませんが、作る過程でさまざな「気づき」があります
プログラミング言語の新たな可能性に気づいたり、プログラムの背景にある深遠な理論をかいま見たり、使ったことのないアルゴリズムや技術に触れてみるきっかけになったります
 引用:あなたの知らない超絶技巧プログラミングの世界 P.6

このプログラムも、Tsの型システムを使用した実用的ではない遊びです。(超絶技巧とはほど遠い)
この実用的ではないプログラムが誰かの気づきを育むプログラムとして動くことを願っております。

あんなコード書いといてなんか偉そうなまとめで嫌ですね。。
私のプログラムはあまりに不格好なので、だれかもっとスマートな答えをお持ちでしたら教えていただけると嬉しいです!!

Discussion