TypeScript 4.8 でやることといえば: negate にていねいに型をつける

TypeScript 4.8 がリリースされました.

これでやることといえば? そう, 数値リテラル型の符号を入れ替えることですね.

type Negate<N extends number> =
    N extends 0 ? 0
  : `${N}` extends `-${infer X extends number}` ? X
  : `-${N}` extends `${infer X extends number}` ? X
  : number;

動作確認.

type Assert<T extends true> = T;
type Compat<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;

type A1 = Assert<Compat<Negate<0>, 0>>;
type A2 = Assert<Compat<Negate<42>, -42>>;
type A3 = Assert<Compat<Negate<-42>, 42>>;
type A4 = Assert<Compat<Negate<6.6e6>, -6.6e6>>;
type A5 = Assert<Compat<Negate<-6.6e6>, 6.6e6>>;
type A6 = Assert<Compat<Negate<42 | -6.6e6>, -42 | 6.6e6>>;
type A7 = Assert<Compat<Negate<number>, number>>;

これを使うと negate(n) みたいな関数にもていねいに型をつけることができます.

function negate<N extends number>(n: N): Negate<N> {
  return -n as Negate<N>;
}

declare const x: 6.6e6;
const y: -6.6e6 = negate(x);

Playground はこちら.

このような型の実装は Improved Inference for infer Types in Template String Types によって可能になりました. 以前は数値リテラル型はコードに直接記述する以外に作ったり操作したりする方法が非常に限定的でした (私の知る限りタプル型を使った自然数の操作に限られていました) が, この機能によって一度文字列リテラル型を経由してではあるもののプログラム的にあらゆる数値リテラル型を扱うことができるようになります.

はてさてどんな遊びに使ってやりましょうか.