型レベルで【#ゆめみからの挑戦状★第10弾】に挑戦する


## #ゆめみからの挑戦状とはなんぞ

最近[株式会社ゆめみ](https://www.yumemi.co.jp/)では、 Twitter にて [#ゆめみからの挑戦状](https://twitter.com/hashtag/%E3%82%86%E3%82%81%E3%81%BF%E3%81%8B%E3%82%89%E3%81%AE%E6%8C%91%E6%88%A6%E7%8A%B6) という企画を定期開催しています
ゆめみの社員がプログラミングに関する問題を出して、皆さんに引用リツイートで答えていただくというものです

### #ゆめみからの挑戦状★第10弾

https://twitter.com/yumemiinc/status/1597068600577519616?s=20&t=Q34xI7ExsScwW_ZHoFmI9g

```javascript
// ワンライナーでkeyとvalueを入れ替えてください。

const keyVal = {
  "gu": "グー",
  "choki": "チョキ",
  "pa": "パー"
}

const valKey = /* ここに解答を書いてください */

console.log(valKey)
//       ↓
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 
```

出題内容は、 `{[key: string]: string}` なオブジェクトの key と value をワンライナーで入れ替えるというものです

### JavaScript での回答例

https://twitter.com/yumemiinc/status/1599587302585831424?s=20&t=Q34xI7ExsScwW_ZHoFmI9g

## 本記事の挑戦内容

JavaScriptのプログラムとしてではなく、TypeScriptの **型** で挑戦します

なお、[株式会社ゆめみ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/yumemi) ではありますが、出題者としてではなく、１人の挑戦者として挑む **非公式** なものです

## 問題：TypeScript の型バージョン

```typescript
// ワンライナーでkeyとvalueを入れ替えてください。

type KeyVal = {
  "gu": "グー",
  "choki": "チョキ",
  "pa": "パー"
}

type ValKey = /* ここに解答を書いてください */
//   ^?
//       ↓
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 
```

### 1. 値のユニオン型を得る

まずは `KeyVal` から `'グー'|'チョキ'|'パー'` を得ます

オブジェクトの型から値の型を得るには [Indexed Access Types](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html) を使います

```typescript
// 'gu' プロパティの型を得るにはこのように書く
type Gu = KeyVal['gu']
//   ^? "グー"

// 'gu' | 'choki' | 'pa' プロパティの型を得るにはこのように書く
type Values = KeyVal['gu' | 'choki' | 'pa']
//   ^? "グー" | "チョキ" | "パー"
```

オブジェクトのすべてのキーをユニオン型で得るには [keyof 型演算子](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html) を使用すればよいので、 `KeyVal` 型の取りうる値のユニオン型はこのように書けます

```typescript
type Values = KeyVal[keyof KeyVal]
//   ^? "グー" | "チョキ" | "パー"
```

### 2. 値をキーにしたオブジェクトの型を作る

次に `'グー'|'チョキ'|'パー'` をキーにしたオブジェクトを作ります

キーを反復処理してオブジェクトの型を作るには [Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#handbook-content) を使います

```typescript
{[P in K]: T}
```

このような構文で、 `P` は新しく定義される型パラメータで `K` の取りうる型を反復しその型が入ります
また `P` は `T` の中で使用できます

```typescript
// 'a' 'b' 'c' を反復し 'a' 'b' 'c' がそれぞれキーとなるオブジェクト
type ValKey = {[Key in 'a' | 'b' | 'c']: any}
//   ^?
// {
//   "a": any;
//   "b": any;
//   "c": any;
// }
```

`Values` ( `'グー'|'チョキ'|'パー'` ) を使うとこのようになります

```typescript
type ValKey = {[Val in Values]: Val}
//   ^?
// {
//   "グー": "グー";
//   "チョキ": "チョキ";
//   "パー": "パー";
// }
```

### 3. 値からキーを探す

オブジェクトの値からキーを探してみます
まずは 特定の値で `KeyVal` の各プロパティの値をフィルタリングします

ある型が別な型と互換性があるかどうかで型を分岐させたいときは [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#handbook-content) を使用します

```typescript
SomeType extends OtherType ? TrueType : FalseType
```

このような構文で、 `SomeType` が `OtherType` と互換性がある場合 `TrueType` ない場合 `FalseType` になります

いままでに出てきた、 `keyof` 、 `Mapped Types` 、 `Indexed Access Types` と組み合わせて、 特定の値で `KeyVal` の各プロパティの値をフィルタリングして、更に値の部分をキーにします

存在しないようにしたいプロパティの値は `never` にしておきます

```typescript
type Val = 'チョキ'
type Filtered = {[Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never}
//   ^?
// {
//   "gu": never;
//   "choki": "choki";
//   "pa": never;
// }
```

そして、さらに `Indexed Access Types` を使い、取りうる値の型を取得すると `never` は無視され目的のキーを得られます

```typescript
type Val = 'チョキ'
type FoundKey = {[Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never}[keyof KeyVal]
//   ^? "choki"
```

### 4. １つにまとめてワンライナーにする

今までの内容を１つにまとめてワンライナーにするとこうなります

```typescript
type ValKey = {
  [Val in KeyVal[keyof KeyVal]]: {
    [Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never
  }[keyof KeyVal]
}
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 
```

https://www.typescriptlang.org/play?#code/PTAEnuGRnhkS4ZBKGQphkD8MhzBgNYFMCegLBgG4EMAbAV3UCSGQU0VAYhkH+zQCQZAzBkHkGQAwZBVBkBEGQIAYAoHgF0wAHdKADSWAGpFQAXlABvHqFAAiAObFVALjWAGhkSqANMrUBjABYB7VAEsdawIMMgc4ZAtQzHTqofgerAiwyGPAC+fIIioNKEEphyigDakaC2AHbiUkRxGJhWAGZpmJEAuoW6CnHRSalZuflFutGR5ViFoOgAHvzoyQAmAM4RMgD8+aC6yei46ABOQZlYNQ1EhSEgKqAAeoM8QA

## 終わりに

[#ゆめみからの挑戦状](https://twitter.com/hashtag/%E3%82%86%E3%82%81%E3%81%BF%E3%81%8B%E3%82%89%E3%81%AE%E6%8C%91%E6%88%A6%E7%8A%B6) は今後も出題されると思います
型レベルで解く必要は１ミリもないのでぜひ軽い気持ちでご参加ください
(逆に型レベルプログラミングに興味がでた方は [type challenges](https://github.com/type-challenges/type-challenges) に挑戦だ！)