今更type-challengesをやる -初級編①-

業務でTypeScriptを使った開発をしているのだが、複雑な型を扱いたい時に「TypeScriptへの型の理解が足りないなあ」と感じることが何度かあった。「そういえばtype-challengesとかやってないなあ」ということに思い至ったので、今更ながらにtype-challangesをやってみる。
各問題の詳しい解説などはいくらでも先行事例があると思われるので、ここでは個人的な感想を書いていくことにする。
type-challengesを解くのにあたっては、mosya<TC>を利用した。

github.com

Readonly

TypeScript組み込みのユーティリティ型であるReadonly<T>を自分で実装してみようという問題。
結論から言えば、これはkeyof型演算子とMapped Typesの組み合わせによって表現することができる。

typescriptbook.jp
typescriptbook.jp

keyof型演算子とMapped Typesの組み合わせは頻出であり、Readonly<T>がこれらの組み合わせで実装されていることも、サバイバルTypeScriptを読んでいたときに認知していたので難しくはなかった。

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

Length of Tuple

タプルTを受け取って、その長さを返す型Length<T>を実装しようという問題。
これには少々面食らってしまった。というのも、型の操作だけで長さを導く方法なんて検討もつかなかったからだ。
しかし冷静に考えてみると、あるタプルの長さを取り出したい時はsomething.lengthのように、lengthプロパティを使用して長さを取り出している。言い換えるとこれはつまり、タプルにはlengthという名前のプロパティが存在しているという意味に他ならない。ということは、タプルが持つlengthというプロパティの型を知ることができればこの問題は解けそうである。
TypeScriptではインデックスアクセス型によってオブジェクトの特定のプロパティの型を参照できることを踏まえれば、あとはもう簡単だった。

typescriptbook.jp

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

公式ドキュメントでも、TypeScriptにおけるタプルが特定のインデックスに対してプロパティを宣言し、数値リテラル型によって長さを宣言するようなArrayと見做せることについて言及されている。

www.typescriptlang.org

Tuple to Object

タプルを受け取り、その各値のkey/valueを持つオブジェクトの型に変換する型を実装しようという問題。
これもMapped Typesを使って解くことができそうだが、Readonlyと違って今回はkeyof型演算子を使えないのがミソっぽい。タプル型の値から要素の型を抽出する方法だが、ここでもインデックスアクセス型が活躍する。

typescriptbook.jp

落とし穴として、今回は受け取ったタプルの要素がオブジェクトのキーとなるので、任意のタプルを受け取れるようにするとエラーが発生するケースがある。受け取れるタプルの要素の型をPropertyKey型で制約しておけば問題なさそう。

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

Push

Array.pushの型バージョンを実装しようという問題。
型でもスプレッド構文が使えることを知っていれば特筆すべきことはなさそう?

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

厳密にはこの型バージョンのスプレッド構文のようなものはTypeScript 4.0で実装されたもので、Variadic Tuple Typesと呼ばれるらしい。

www.typescriptlang.org

Unshift

Array.unshiftの型バージョンを実装しようという問題。
Pushと同じことをやるだけ。

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

Pick

TypeScript組み込みのユーティリティ型であるPick<T, K>を自分で実装してみようという問題。
これもMapped Typesを使ってあげれば難なく書けそう。

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

インデックスアクセス型であるT[P]の部分ではTに存在しないキーを指定することはできないので、あらかじめKの型をTのキーで制約しておく必要があるのがポイントか。

ここまでの感想

初級編と言いながら、結構考えさせられる問題が多い印象。どれもTypeScriptの基本的な機能を使って解けるとはいえ、その基本部分をしっかり押さえていないと手も足も出なさそう。