27
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

HRBrainAdvent Calendar 2022

Day 7

type-challengesの「Permutation」で詰まった話

Last updated at Posted at 2022-12-06

はじめに

この記事はHRBrain Advent Calendar 2022カレンダー2の7日目の記事です。

株式会社HRBrainでは毎週TypeChanllengeを行っています。
この取り組みを紹介した記事があるので興味がある方は御覧ください。

その中で「Permutation」という特段難しい問題に詰まったので紹介します。
復習がてら記事にしました。

type-challengesとは

TypeScriptの型の問題集のようなリポジトリです。
Vue.js コアチームメンバーでVitest、Slidevの作成者である@antfu7さんという方が開発しています。

Permutation

Union 型を Union 型の値の順列を含む配列に変換する順列型を実装する問題です。

type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']

参考にさせていただいた回答

こちらが最もコメントの多く、recommendのラベルが付けられている回答です。
本記事もこちらを参考にさせて頂いています。
丁寧に解説してくださっているので、興味がある方は下記のリンクを御覧ください。

type Permutation<T, K = T> = [T] extends [never]
    ? []
    : K extends K
    ? [K, ...Permutation<Exclude<T, K>>]
    : never;

解説

まとめては理解しにくいと思うので分けて解説します。

[T] extends [never] ? [] : K

type Permutation<T, K=T> =
    [T] extends [never] ? [] : K 

もう既にわけがわかりませんね
これは一言でいうとTがneverか判定している処理です。

下記の式ででいけるのでは?と思う方がいるかも知れませんが、期待通りの動作はしません。
TSのPlaygroundを見ていただくと理解が早いと思います。

T extends never ? [] : K 

型引数に渡されたneverは条件分岐の際、無視されるので機能しません。
そのため評価される前にタプル型にして、条件分岐できるようにしています。

かなりhack的な方法ですが、neverを判別するにはこれしか無いようです。

詳しくはこちらをご覧ください。

K extends K

type Permutation<T, K = T> = K extends K
    ? [K, ...Permutation<Exclude<T, K>>]
    : never;

TypeScriptはunion型を型引数に渡すと下記のように分散する性質があります。

 T extends U ? X : Y 
 T = 'A' | 'B'

(A extends U ? X : Y) | (B extends U ? X : Y)

それを利用してK extends Kにunion型を渡してそれぞれ条件分岐を行わせることができます。
このように渡されたunion型でループ処理のようなことをさせています。

? [K, ...Permutation>]: never

[K, ...Permutation<Exclude<T, K>>]

'A'が型引数Kに渡されたとします。
再帰的にPermutationの引数としてT('A' | 'B' | 'C')からK('A')を除外した'B' | 'C'を型引数として渡しています。
配列に K('A')とPermutationの結果を展開しています。

この再帰的な流れを理解するのに下記の表がとてもわかりやすかったのでぜひご覧下さい。

最終的に全てのパターンが結合され順列を網羅した型になります。

['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']

さいごに

type-challengesをやっていて着実に型表現力がついてきていると日々実感しています。
TypeScriptに自身がない方、TS力を上げたい方は一度チャレンジしてみてはいかがでしょうか?

27
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?