TypeScriptのユーティリティ型を再実装する Part3

# はじめに
TypeScriptではよく行われる型変換をサポートするために、[ユーティリティ型](https://www.typescriptlang.org/docs/handbook/utility-types.html)というものを提供しています。この記事ではそんなユーティリティ型を再実装してみようと思います。全部で21ケースありますが、1ケース(`ThisType`)は単なる空のマーカーインターフェイスで、4ケースはintrinsic型(TypeScriptのコンパイラに隠された型)なので残りの16ケースを紹介します。この記事はPart3です。Part1は[こちら](https://qiita.com/KokiSakano/items/65e294173688f977a291)、Part2は[こちら](https://qiita.com/KokiSakano/items/3b7dc5f8f1c905cdfe2d)です。

# ConstructorParameters
`ConstructorParameters`はコンストラクタのパラメータを取得する型です。`Parameters`が関数だったのに対して、これはコンストラクタに作用する型です。縦横の長さを持つ長方形を彷彿させるようなクラスを考えます。
```ts
class Rectangle {
  height: number;
  width: number;

  constructor(height: number, width: number) {
    this.height = height;
    this.width = width;
  }
}

type Sample01 = ConstructorParameters<typeof Rectangle>;
```
この場合Sample01の型は`[height: number, width: number]`となります。もし、コンストラクタが定義されていないクラスであればこの型は空配列を返します。実装は`Parameters`とほとんど同じです。
```ts
type MyConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
```
`T`が`(...args: any) => any`から`abstract new (...args: any) => any`に変わりました。`new`の部分でコンストラクタの部分を取ってます。`abstruct`は抽象クラスにも対応するために追加されています。
これもTypeScriptの実装と同じです。

# ReturnType
`ReturnType`は関数の戻り値を取得する型です。
```ts
type SampleFunc = (arg1: string, arg2: number) => string | number;
type SampleReturnType = ReturnType<SampleFunc>;
```
`SampleReturnType`は`string | number`になります。
`Parameter`の返り値バージョンとなっています。`Parameter`では無視した部分を取得することで型を作成することが出来ます。
```ts
type ReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R
    ? R
    : any;
```
`Parameter`ではargsの部分を取得していた部分を返り値を取得するようにしました。改行を除くとTypeScriptと同じ実装になっています。

# InstanceType
`InstanceType`はインスタンス型を取得します。
```ts
class Rectangle {
  height: number;
  width: number;

  constructor(height: number, width: number) {
    this.height = height;
    this.width = width;
  }
}

type Sample01 = InstanceType<typeof Rectangle>;
```
`Sample01`は`Rectangle`になります。`InstanceType`はコンストラクタの帰り値から取得します。つまりこの関数は`Parameters`、`ConstructorParameters`、`ReturnType`ときたので`ConstructorReturnType`に値します。
```ts
type MyInstanceType<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: any) => infer R
    ? R
    : any;
```
改行を除くとTypeScriptと同じ実装になっています。

# ThisParameterType
`ThisParameterType`はその関数、メソッドが持つ`this`の型を取得します。
```ts
type Sample01 =
  ThisParameterType<(this: { height: number, width: number }, area: () => number) => void>
```
のようにすると`this`の部分だけ、つまり`{ height: number, width: number }`が得られます。
ちなみにですが、`Parameters`を使って
```ts
type Sample02 =
  Parameters<(this: { height: number, width: number }, area: () => number) => void>
```
のようにするとthisの部分は得られず`[area: () => number]`しか得られません。`this`は他の引数とは異なり明示的に指定して取得する必要があるからです。つまり実装は以下のようになります。
```ts
type MyThisParameterType<T> =
  T extends (this: infer U, ...args: any) => any
    ? U
    : unknown;
```
`...args`を置いておいて`this`から明示的に型を取得してそれを返すようにしています。
TypeScriptの実装ニノいて`..args`の型は`never`になっています。ここだけ`never`になっている理由はわからないです(実装はできるだけTypeScriptの実装に寄せてanyを使ってましたが、そもそも使わない型は全部neverでいいと考えています)。

# OmitThisParameter
`OmitThisParameter`は引数から`this`がないものとした型が返ってきます。
```ts
type Sample01 =
  OmitThisParameter<(this: { height: number, width: number }, area: () => number) => void>
```
以上の例だと`(area: () => number) => void`が返ってきます。引数が`Parameters`で取得できるものだけになっていますね。`Parameters`と`ReturnType`を活かして以下のように作ることもできます。
```ts
type MyOmitThisParameter01<T> =
  T extends (...args: any) => any
    ? (...args: Parameters<T>) => ReturnType<T>
    : T;
```
他の型に依存したくないときは下のようにします。
```ts
type MyOmitThisParameter02<T> = T extends (...args: infer P) => infer R ? (...args: P) => R : T;
```
TypeScriptの実装では以下のようになっています(一部改行しています)。
```ts
type OmitThisParameter<T> =
  unknown extends ThisParameterType<T>
    ? T
    : T extends (...args: infer A) => infer R
      ? (...args: A) => R
      : T;
```
`MyOmitThisParameter02`とほとんど同じですが、`ThisParameterType`で`this`を持っていないか確認しています。ない場合は何もせずにすぐ返しています。これによるメリットはいまいちわかりませんでした(関数を作り直すことはthisから値を抜き出すことに対してコストがかかるとかの理由があるんですかね？)。

# おまけ
再実装しなかった型についても簡単に紹介します。
# ThisType
`ThisType`はこれまでのような渡された型からなんらかの型を生成するような型ではありません。この型は以下のように使います。
```ts
declare function MakeFunctionObject<Data, Method>(options: {
  data: Data;
  methods: Method & ThisType<Data & Method>;
}): Data & Method;
 
const rectangle = MakeFunctionObject({
  data: { height: 8, width: 10 },
  methods: {
    area() {
      return this.height * this.width;
    },
    outputArea() {
      console.log(this.area())
    }
  },
});
```
`MakeFunctionObject`は`data`に値を`methods`にそれらを扱うメソッドを用意するような関数をです。`methods`では`data`の値を`this`でアクセスしたり、`methods`内で定義したメソッドに`this`でアクセスできるようになっています。この`this`の型を定義するのが`ThisType`です。
この例では`MakeFunctionObject`を利用して`rectangle`を作っています。`data`で縦幅と横幅の定義、`methods`で面積とその出力する関数の定義を行なっています。この関数の場合`Data`は`{ height: number, width: number }`、`Method`は`{ area(): number, outputArea(): number }`となります。そして`ThisType<Data & Methods>`となるので、`methods`内の`this`は
```ts
{
  height: number,
  width: number,
} & {
  area(): number,
  outputArea(): number,
}
```
となります。このように`ThisType`を用いてあるオブジェクト内で利用する`this`の型を決定することが出来ます。
なお、`Method & ThisType<Data & Method>`となっていますが、ThisTypeが返す型は`{}`なので`Method`に影響はありません。
## Uppercase
受け取った文字列を全て大文字にする型です。
```ts
type Sample01 = Uppercase<'✋hello'>;
type Sample02 = Uppercase<'✋HEllo'>;
type Sample03 = Uppercase<'✋HELLO'>;
```
これらは全て`✋HELLO`型になります。
## Lowercase
受け取った文字列を全て小文字にする型です。
```ts
type Sample01 = Lowercase<'✋hello'>;
type Sample02 = Lowercase<'✋HEllo'>;
type Sample03 = Lowercase<'✋HELLO'>;
```
これらは全て`✋hello`型になります。
## Capitalize
受け取った文字列の先頭を大文字にする型です。
```ts
type Sample01 = Capitalize<'hello'>;
```
この例では`Hello`が得られます。
```ts
type Sample02 = Capitalize<'✋hello'>
```
では何も変わらず`✋hello`が得られます。
この型は`Uppercase`を使って以下のように実装できます。
```ts
type MyCapitalize<S extends string> =
  S extends `${infer T}${infer U}`
    ? `${Uppercase<T>}${U}`
    : S;
```
## UnCapitalize
受け取った文字列の先頭を小文字にする型です。
```ts
type Sample01 = UnCapitalize<'Hello'>;
```
この例では`hello`が得られます。
この型も`Lowercase`を用いて実装出来ます。
```ts
type MyCapitalize<S extends string> =
  S extends `${infer T}${infer U}`
    ? `${Lowercase<T>}${U}`
    : S;
```