typescriptの型を関数だと考える

template literal typesの勉強で以下のような型を書いていました。
`FF<"FF/7/charactor">` とすると、 `"ジタン" | "ビビ"` という型が返ってくるという使い道が全くない型です。

```ts
type Title = 'FF'
type Numbering = '7' | '8'
type Charactor = 'charactor'
type Monster = 'monster'
type MenuList = Charactor | Monster
type FF7Charactors = 'ジタン' | 'ビビ'
type FF7Monsters = 'クジャ' | 'ペプシ'
type FF8Charactors = 'スコール' | 'リノア'
type FF8Monsters = 'アデル' | 'アルティミシア'

type FF<S extends string> =
  S extends `${Title}/${infer Rest}`
    ? Rest extends `${infer Num}/${infer Menu}`
      ? Num extends Numbering
        ? Num extends '7'
          ? Menu extends MenuList
            ? Menu extends Charactor
              ? FF7Charactors
              : Menu extends Monster
                ? FF7Monsters
                : unknown
            : unknown
          : Num extends '8'
            ? Menu extends MenuList
              ? Menu extends Charactor
                ? FF8Charactors
                : Menu extends Monster
                  ? FF8Monsters
                  : unknown
              : unknown
            : unknown
        : unknown
      : unknown
    : unknown

type FF7Charactor = FF<"FF/7/charactor">
type FF7Monster = FF<"FF/7/monster">
type FF8Charactor = FF<"FF/8/charactor">
type FF8Monster = FF<"FF/8/monster">

let sevenCharacter: FF7Charactor = 'ジタン'
let sevenMonster: FF7Monster = 'クジャ'
let eightCharactor: FF8Charactor = 'スコール'
let eightMonster: FF8Monster = "アデル"
```

つかってる物としては Conditional Types と infer のみで愚直に書いていますが、昔の僕だとこれくらいの型でも「何を書いてるか全然わからん！」という気持ちになっていました。
しかし、この型定義を関数だと思い込むことで結構理解が進みました。上記の型を疑似コードで表現すると、こんな感じになります。

```ts:tsっぽい疑似コード
const Title = 'FF';
const Numbering = ['7', '8'];
const Charactor = 'charactor';
const Monster = 'monster';
const MenuList = [Charactor, Monster];
const FF7Charactors = ['ジタン', 'ビビ'];
const FF7Monsters = ['クジャ', 'ペプシ'];
const FF8Charactors = ['スコール', 'リノア'];
const FF8Monsters = ['アデル', 'アルティミシア'];

const FF = (s: string) => {                        // type FF<S extends string> =
  if (s === `${Title}/${infer Rest}`) {            // S extends `${Title}/${infer Rest}`
    if (Rest === `${infer Num}/${infer Menu}`) {   // Rest extends `${infer Num}/${infer Menu}`
      if (Numbering.includes(Num)) {               // Num extends Numbering
        if (Num === '7') {                         // Num extends '7'
          if (MenuList.includes(Menu)) {           // Menu extends MenuList
            if (Menu === Charactor) {              // Menu extends Charactor
              return FF7Charactors
            } else if (Menu === Monster) {         // Menu extends Monster
              return FF7Monsters
            }
          }
        } else if (Num === '8') {                  // Num extends '8'
          if (MenuList.includes(Menu)) {
            if (Menu === Charactor) {
              return FF8Charactors
            } else if (Menu === Monster) {
              return FF8Monsters
            }
          }
        }
      }
    }
  }
}
```

- 型定義は `変数` 。
- ジェネリクスを用いた型は `関数`。
- ジェネリクスが必要としている型は `関数の引数`。
- extends と Conditional Types の組み合わせは `if文`。

とイメージするとわかりやすいと思いました。
また別のコード例をとりあげます。

- 再帰的な型は `繰り返し`

という捉え方をすると以下の型定義が理解しやすくなりました。
配列の最後の値の型を取り出す TakeLast という型の場合、

```ts
type TakeLast<V> =
  V extends []
    ? never
    : V extends [number]
      ? V[0]
      : V extends [number, ...infer R]
        ? TakeLast<R>
        : never;

type Three = TakeLast<[1, 2, 3]>;

const three: Three = 3;
```

tsの関数に変換すると、こうなります。

```ts

const takeLast = (v: any) => { // type TakeLast<V> =
  if (!Array.isArray(v)) {     // V extends [] (を反転させた条件)
    return;
  }
  if (v.length !== 1) {        // V extends [number] (を反転させた条件)
    v.shift();                 // V extends [number, ...infer R]
    takeLast(v);               // TakeLast<R>
  }
  return v[0];                 // V[0]
};

console.log(takeLast([1, 2, 3])) // 3;
```


こういうイメージを持つことで、複雑な型を見た際の、「全然わからない！」という気持ちが薄れてきたように思います。
今でも、Conditional Types の三項演算子のネストが深くなっていると、思わず拒否反応が出ちゃいますが、慣れだと思っていろんな型に触れていきたいと思いました。