📝

Type Challengesに入門する

2022/12/23に公開

おはようございます。Type Challengesに入門する6日目です。本日の問題は

question.ts
組み込みの型ユーティリティ`Exclude <T, U>`を使用せず、`U`に割り当て可能な型を`T`から除外する型を実装します。

// example
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

ジェネリクスの第一引数から第二引数を除外する問題。今までお世話になったMapped Types
{[P in K]: T}という構文なので今回は使えなそうです。
Mapped TypesでできないならばConditional Typesでできないか考えてみます。
まず、UはTの部分型なので

myExclude.ts
  type MyExclude<T extends U, U> = any

こうしましたが、これ以降なかなかうまくできなかったため回答を見てしまいました。

answer.ts
 type MyExclude<T, U> = T extends U ? never : T

わかっていなかったことが2つありました。1つ目は、Conditional Typesは要素一つ一つに適用されていく(?)感じだということ、2つ目は、1を前提にするとUは常にTの部分型では無いということです。

study.ts
 type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
 
 // 1周目
 'a''a'の部分型である(=T extends Uを満たす)never
 // 2周目
 'a''b'の部分型ではない(=T extends Uを満たさない)'b'(T)
 // 3周目
 'a''c'の部分型ではない(=T extends Uを満たさない)'c'(T)
 // 結果
 never | 'b' | 'c''b' | 'c'

こんな感じで処理されているのでは無いでしょうか?ここに関しては何かで見たわけでは無いので間違っていたらご指摘いただけると幸いです。

Union typesの分配則: Union typesのConditional Typesは、それぞれのConditional TypesのUnionに展開される。すなわち、
(T1 | T2) extends U ? X : Y = (T1 extends U ? X : Y) | (T2 extends U ? X : Y)2

https://qiita.com/Quramy/items/b45711789605ef9f96de
↑の方の記事を参考にすると、

study.ts
 type Result = 'a' extends 'a' | 'b' | 'c' ? never T 'b' | 'a' extends 'a' | 'b' | 'c' ? never T 'a' | 'c' extends 'a' | 'b' | 'c' ? never T 'a'

と書くことができるということなので、先ほどの予想はおそらくあっていると思います。
Conditional Typesについてはまだ理解が浅いので、いつかアウトプットも兼ねて記事にしようと思います。

Discussion