k-tokitoh

2022-05-08

type challenges - warm-up, easy

type-challenges/type-challenges

hello world

type HelloWorld = string;

pick

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

型引数 K に条件として extends…を書かないといけなかった。

readonly

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

tuple to object

type TupleToObject<T extends readonly (string | number | symbol)[]> = {
  [P in T[number]]: P;
};

key は string|number|symbol である必要があるのか。

first of array

type First<T extends unknown[]> = T extends [unknown, ...unknown[]]
  ? T[0]
  : never;

extends で要素がひとつ以上存在することを担保してあげる必要がある。

type First<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;

infer をつかってもよい。 infer は extends の右側で導入する。

length of tuple

type Length<T extends readonly unknown[]> = T["length"];

そっか、length はプロパティだから T["length"]で取り出せるのか。

exclude

type MyExclude<T, U> = T extends U ? never : T;

union distribution って聞いたことあったけど初めて書いた。 extends の左側が型変数で union 型ならばそれは分配される。 never は 0 個の union のように振る舞う。

awaited

MyAwaited<number>をエラーにするためにはシンプルな再帰では書けないので、まず以下で書いた。

type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer U>
  ? U extends Promise<infer V>
    ? V
    : U
  : never;

しかしこれだとネストが 2 階層までに限定されてしまい、例えば以下がとおらない。

type N = Promise<Promise<Promise<string>>>;
Expect<Equal<MyAwaited<N>, string>>;

一番上の階層では型引数に extends を書くことで MyAwaited<number> とかを弾きつつ、内側では再帰で書けばより一般的に書けた。 上記のテストもとおる。

type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer U>
  ? MyAwaitedBase<U>
  : never;
type MyAwaitedBase<T> = T extends Promise<infer U> ? MyAwaitedBase<U> : T;

if

type If<C extends boolean, T, F> = C extends true ? T : F;

concat

type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];

配列を展開するみたいに、配列の型も展開できるんだな。

includes

前段として、型の一致を検査することを考える。参考

type Equal_0<X, Y> = X extends Y ? (Y extends X ? true : false) : false;

type _0_true_0 = Equal_0<0, 0>; // true
type _0_false_0 = Equal_0<0, 1>; // false
type _0_true_1 = Equal_0<0 | 1, 0 | 1>; // boolean

これだと union distribution により最後の例で正しく判定できない。

配列にすることで union distribution を回避する。

type Equal_1<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;

type _1_true_0 = Equal_1<0, 0>; // true
type _1_false_0 = Equal_1<0, 1>; // false
type _1_true_1 = Equal_1<0 | 1, 0 | 1>; // true
type _1_false_1 = Equal_1<0, 0 | 1>; // false
type _1_false_2 = Equal_1<0, any>; // true

extends は割り当て可能性を判定し、最後の例で 0 と any は互いに割り当て可能なため true を返してしまう。

一応、0 と any が互いに割り当て可能であることを確認しておく。

declare let a: 0;
declare let b: any;

a = b; // エラーにならない
b = a; // エラーにならない

ここで「条件付き型同士が割り当て可能になるには extends 直後の型同士が同値でなければならない」という性質がある。

まずそれを確認する。

declare let foo: <T>() => T extends string ? 0 : 1;
declare let bar: <T>() => T extends any ? 0 : 1;

foo = bar; // エラーになる
bar = foo; // エラーになる

extends を直ちに評価せず残すために、便宜的にジェネリックつきの関数の戻り値の型に extends を用いている。

ちなみにこういう関数型を実際に呼び出してみると、ジェネリックパラメータ T は推論しようがないので unknown となる。

foo(); // let foo: <unknown>() => 1

ちなみに t/f の場合の型が同じだと「extends の右側がなんだろうと結局同じ型でしょ」ということで代入可能になる。

declare let foo: <T>() => T extends string ? 0 : 0;
declare let bar: <T>() => T extends any ? 0 : 0;

foo = bar; // エラーにならない
bar = foo; // エラーにならない

さてこの性質を利用して型の一致を判定する。

type Equal_2<X, Y> = (<T>() => T extends X ? 0 : 1) extends <T>() => T extends Y
  ? 0
  : 1
  ? true
  : false;

type _2_true_0 = Equal_2<0, 0>; // true
type _2_false_0 = Equal_2<0, 1>; // false
type _2_true_1 = Equal_2<0 | 1, 0 | 1>; // true
type _2_false_1 = Equal_2<0, 0 | 1>; // false
type _2_false_2 = Equal_2<0, any>; // false

以上で型の一致の検査をすることができたので、本題の includes に戻る。 一致性をひとつひとつの要素について再帰的に判定していく。

type MyEqual<X, Y> = (<T>() => T extends X ? 0 : 1) extends <T>() => T extends Y
  ? 0
  : 1
  ? true
  : false;
type Includes<T extends readonly unknown[], U> = T extends [infer F, ...infer R]
  ? MyEqual<F, U> extends true
    ? true
    : Includes<R, U>
  : false;

push

type Push<T extends unknown[], U> = [...T, U];

unshift

type Unshift<T extends unknown[], U> = [U, ...T];

parameters

type MyParameters<T extends (...args: any[]) => unknown> = T extends (
  ...args: infer Args
) => unknown
  ? Args
  : never;