はじめに
TypeChallengesという型パズルの問題集の中である問題の答えを見たとき、ユニオンの分配法則というものを当時は知らず、「なんだこれ?」となりましたので、記事へ残すことにしました。
そもそもTypeChallengesって?
TypeChallengesの公式サイトによると下記のように書かれています。
このプロジェクトは、型システムがどのように動作するのかを理解したり、独自の型ユーティリティを書いたり、課題へのチャレンジを楽しむことをサポートします。
簡単に言うとお題として出された型を実装しましょうといった感じですかね。
難易度は初級、中級、上級、最上級の4つに分かれています。
正直、初級の時点で難しいです笑(自分のスキルレベルの問題かもしれないですが、、、)
解いた問題
問題はExcludeで、内容としては以下です。
// 組み込みの型ユーティリティExclude <T, U>を使用せず、Uに割り当て可能な型をTから除外する型を実装します。
// 例:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// 満たすケース(Equalの第一引数に対して第二引数の値が返ってくればOK)
type cases = [
Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, 'b' | 'c'>>,
Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, 'c'>>,
Expect<Equal<MyExclude<string | number | (() => void), Function>, string | number>>,
]
答えをみたものの理解できず、、、
解いてもわからなかったので答えを見てました。
type MyExclude<T, U> = T extends U ? never : T;
ですがこれを見ても僕は「?」でした。
「え、これだったらほぼneverにならないか?」と思ってしまったのです。
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'じゃなくてneverにならないか?
正体はユニオン分配法則だった
同じような形のコードをもとに調べていくとどうやらユニオン分配法則とやらが関係していることがわかりました。
ユニオン分配法則とは
ユニオン分配はジェネリクスで使われる型変数Tに対しユニオン型が指定された場合、その各要素に対してジェネリクスの型変数を適用することを指します。
サバイバルTypeScriptによると上記のように記載されています。
MyExclude
の例をつかうなら、Tに'a' | 'b' | 'c'
が入った場合、
'a' | 'b' | 'c'
型としていっぺんに評価するのではなく、
'a'
,'b'
,'c'
それぞれ分配して評価するようです。
イメージ
以下のような感じで評価されているみたいです。
// 「T」に「"a" | "b" | "c"」、「U」に「"a"」を入れた場合
type MyExclude<T, U> = T extends U ? never : T;
type MyExclude<"a" | "b" | "c", "a"> = "b" | "c"
// 実は以下のように分配して評価されていたため、上記の値になる
"a" extends "a" ? never : "a"; // never
"b" extends "a" ? never : "b"; // "b"
"c" extends "a" ? never : "c"; // "c"
調べる際、こちらの記事を参考にさせていただきました。ありがとうございました。
おわりに
最初、ユニオン分配法則を知らなかったため、なんでそのような答えになるのかわかりませんでした。
ただ、知識が増えたということで、結果OKとします笑
まだまだ学ぶことが多いですが、引き続きTypeScriptについては理解を深めていきたいと思います!