Type Challenges を解く【初級編】

## はじめに

TypeScript の型について、雰囲気でしか理解していない節があるので Type Challenges をやってみることにしました。

とりあえず初級編を解いてみたので、自分の解法と解説を書いていきます。

## Type Challenges について

TypeScript の型についての問題を集めたリポジトリです。
ありがたいことに日本語対応がされています。

https://github.com/type-challenges/type-challenges/blob/main/README.ja.md

詳しい説明は以下の記事を参照すると良いです。

https://qiita.com/ryo2132/items/925b96838dd8cca7cebd

## お試し

初級編の前にお試しの問題があるので、まずはこれを解いて Type Challenges の解き方を理解します。

### 13・Hello World

https://github.com/type-challenges/type-challenges/blob/main/questions/00013-warm-hello-world/README.ja.md

`HelloWorld` という型を `string` 型にするだけの問題です。

コードを書いて確認するために `挑戦する` と書かれた青いボタンを押します。
そうすると TS Playground が開き、問題文・解答用コード・テストコードが書かれている状態になっています。
テストは初期状態だと型チェックのエラーが出ているので、エラーが無くなるように解答を変更できれば、正解となります。

実際に下記のように解答を変更すると、エラーが無くなることを確認できると思います。

```diff_typescript
- type HelloWorld = any // expected to be a string
+ type HelloWorld = string // expected to be a string
```

## 初級編

お試しの Hello World を解くことができたので、ここからは初級編を解いていきます。

### 4・Pick

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.ja.md)

まず `K` を `T` のプロパティのキーのみにしたくなるので `keyof T` を用いて `K` の型を制限します。

```ts
type MyPick<T, K extends keyof T> = any
```

そして `K` で与えられる各プロパティのキーに応じて、プロパティの値の型を `T` と合うようにしたいので Mapped Types で `K` を反復処理し Indexed Access Types で `T` から対応するプロパティの値の型を取り出します。

```ts
type MyPick<T, K extends keyof T> = {
  [key in K]: T[key]
}
```

### 7・Readonly

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.ja.md)

[4・Pick](#4pick) と似たようなことを行い、プロパティの先頭に `readonly` を付けます。

```ts
type MyReadonly<T> = {
  readonly [key in keyof T]: T[key]
}
```

### 11・Tuple to Object

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.ja.md)

`T` をプロパティのキーとして使える型に制限したいので `string | number | symbol` を表す `PropertyKey` 型を使います。

```ts
type TupleToObject<T extends readonly PropertyKey[]> = any
```

そして Indexed Access Types は `T[number]` とすることで、配列の要素の型を取得できるので、これを用いて今までと似た処理をします。

```ts
type TupleToObject<T extends readonly PropertyKey[]> = {
  [K in T[number]]: K
}
```

### 14・First of Array

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00014-easy-first/README.ja.md)

配列の最初の要素を取るだけなので `T[0]` で良さそうです。

```ts
type First<T extends any[]> = T[0]
```

テストケースをよく見ると、配列が空のときは `never` である必要があるようなので Conditional Types で条件分岐をします。

```ts
type First<T extends any[]> = T extends [] ? never : T[0]
```

### 18・Length of Tuple

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.ja.md)

Indexed Access Types で `length` プロパティを参照します。
タプルは固定長なので `length` に対応する型は具体的な要素数になっています。

```ts
type Length<T extends readonly any[]> = T['length']
```

### 43・Exclude

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00043-easy-exclude/README.ja.md)

Distributive Conditional Types によって実現できます。

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

### 189・Awaited

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.ja.md)

`infer` を用いることで `Promise` の中の型を取ることができます。

```ts
type MyAwaited<T> = T extends Promise<infer U> ? U : T
```

テストケースには `Promise` の中に `Promsie` があるものもあるので、再帰で処理します。

```ts
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
```

テストケースをよく見てみると `Promise` と同じようなオブジェクトの型もあるので `PromiseLike` を使うことにします。

```ts
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T
```

### 268・If

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.ja.md)

`C` を `boolean` に制限して Conditional Types で条件分岐をします。

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

### 533・Concat

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00533-easy-concat/README.ja.md)

Variadic Tuple Types によって実現できます。

```ts
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U]
```

### 898・Includes

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.ja.md)

再帰によって一つずつ条件に合う型が無いかを見ていけばよさそうです。

```ts
type Includes<T extends readonly any[], U> =
  T extends [infer First, ...infer Rest]
    ? First extends U
      ? true
      : Includes<Rest, U>
    : false
```

テストケースをよく見ると配列に `boolean` などの Union Types が入っています。
Union Types があるということは条件分岐で Distributive Conditional Types が働いてしまいます。
この働きを抑制するために `extends` の両側の型を `[]` で囲みます。

```ts
type Includes<T extends readonly any[], U> =
  T extends [infer First, ...infer Rest]
    ? [First] extends [U]
      ? true
      : Includes<Rest, U>
    : false
```

`U` にも Union Types が入るみたいなので `[U] extends [First]` も確認します。

```ts
type Includes<T extends readonly any[], U> =
  T extends [infer First, ...infer Rest]
    ? [First] extends [U]
      ? [U] extends [First]
        ? true
        : Includes<Rest, U>
      : Includes<Rest, U>
    : false
```

条件分岐を減らすために少し工夫をします。

```ts
type Includes<T extends readonly any[], U> =
  T extends [infer First, ...infer Rest]
    ? [First, U] extends [U, First]
      ? true
      : Includes<Rest, U>
    : false
```

`readonly` 修飾子の有無も区別する必要があるみたいです。
それなりに調べてみましたが、明らかに初級編とは言えない解決策しかなさそうなので、テストケースで使われている `Equal` を使って解きます。

```ts
import type { Equal } from '@type-challenges/utils'

type Includes<T extends readonly any[], U> =
  T extends [infer First, ...infer Rest]
    ? Equal<First, U> extends true
      ? true
      : Includes<Rest, U>
    : false
```

テストケースが追加されて難しくなってしまったみたいです。
以下のものが実装されたら、初級編と言えそうな気がします。

https://github.com/microsoft/TypeScript/issues/48100

### 3057・Push

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/03057-easy-push/README.ja.md)

Variadic Tuple Types によって実現できます。

```ts
type Push<T extends any[], U> = [...T, U]
```

### 3060・Unshift

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/03060-easy-unshift/README.ja.md)

[3057・Push](#3057push) と殆ど同じです。

```ts
type Unshift<T extends any[], U> = [U, ...T]
```

### 3312・Parameters

[問題](https://github.com/type-challenges/type-challenges/blob/main/questions/03312-easy-parameters/README.ja.md)

問題を TS Playground で開くと `T extends (...args: any[]) => any` と最初から書かれています。
今回求めているのはこの `args` の型なので `infer` を使います。

```ts
type MyParameters<T extends (...args: any[]) => any> =
  T extends (...args: infer U) => any
    ? U
    : never
```

## おわりに

解くために調べていくうちに、理解の浅かった部分の補強がかなりできたと思います。

次は中級編ですが、問題数が 100 近いので、適当なタイミングで記事にしたいと思います。