ひびのログ

日々ではないけどログを出力していくブログ

型引数が整数かどうか調べる型関数

を作ったのでご紹介。 先人がいそうだけど、自分なりに考えて作ったので残しておく。

コード

Playground Link

type Num = `${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0}`
type Token = `${Num | "-" | "+"}`

type IsInt<N extends string> =
  N extends Num ? true
  : N extends `${infer T}${infer S}` ?
    T extends Token ?
      IsInt<S>
    : false
  : false

type toNumber<T extends string> =
  T extends `${infer N extends number}` ? N : never

// 本体:型引数に与えられた数(文字列)が、整数であればそれを、整数でなければ never を返す
type Int<N extends number | string> =
  IsInt<`${N}`> extends true ? toNumber<`${N}`> : never

// ------------------------------------------------------------
// tests
// ------------------------------------------------------------
// OK
const ok1: Int<1> = 1
const ok2: Int<10> = 10
const ok3: Int<100> = 100
const ok4: Int<-1> = -1
const ok5: Int<"99"> = 99

// NG(never ならテスト OK)
type ng1 = Int<1.1>
type ng2 = Int<"1.0">
type ng3 = Int<"-">

知見

改めて、TS の方の表現力がすごい。 Template Literal Types の底力を見せてもらった。 「こんなことできるかなー」と思って試してみた toNumber が通って驚いた。 infer って extends で型の絞り込みできるんだ……

困ったこと。 型のテストはあまりやったことがなく、never になることを確かめる書き方が分からなかった。 普段の業務では(あえて)複雑な型を扱わないので、調べたことがなかった。

余談だが、フォーマッターは Prettier の experimental 機能である --experimental-ternaries フラグを使った。 まだ慣れてないけど、ルールが単純で読みやすい。 JSer にはあまり馴染みがないけど、Ruby のように ? を後置する(文化のある)言語出身者は一瞬で馴染みそう。

今のところ不満があるとすれば、? のあと同じ行に then 部分が来ると、「行の最後に ? があれば if 部分」という読み方ができないこと。 これは if 部分であることを先に読み取ろうとする自分の癖が原因かもしれないので、引き続き使ってみて様子を見てみたい。

sosukesuzuki.dev