型における空集合、never型とは
never
型とは
never
型は、「決して観測されない値」、「該当する値が存在しない」ことを表す型です。
never
型の変数にはany
型の値であっても代入することはできません。
const anyValue: any = 271
const neverValue: never = anyValue //Type 'any' is not assignable to type 'never'.(2322)
never
型の変数を引数に設定した場合には渡せる引数が存在しないため[1]、関数を呼び出すことができなくなります。
function neverArgFunction(arg: never){
return 314
}
const num = neverArgFunction(1) //Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
const numAsNever = neverArgFunction(1 as never) //型アサーションによってコンパイルエラーを握りつぶすことはできます。
console.log(numAsNever) //314
never
型はあらゆる型の部分型
型注釈や型アサーションによってnever
型の変数を用意した場合に、このnever
型の変数は、あらゆる型の変数に代入することが可能です。
このことから、never
型があらゆる型の部分型として扱われていることがわかります。集合論において、空集合はあらゆる集合の部分集合ですが、never
型も同様の立ち位置にいるわけですね。
const neverValue: never = 1 as never
const str: string = neverValue
const obj:{str: string, num: number} = neverValue
function neverArgFunction(arg: never){
const num: number = arg
const obj: {str: string, num: number} = arg
return 314
}
// コンパイルエラーが発生しない
never
型とユニオン型
number | never
のようなnever
とのユニオン型については、never
の部分が無視された形の型となります。
type UnionNever = string | number | never
const num: UnionNever = 1
型注釈:UnionNever
をつけてVS Codeなどでカーソルを合わせてみると、type UnionNever = string | number
のようにnever
部分が無視されて表示されます。
never
型を空集合と捉えると、他の集合Aとの和集合|
をとったときにその結果が集合A自身となることに対応していると捉えられると思います。
また、never
型は、ユニオン型の変数について型情報で条件分岐させていったときに、ユニオンのすべての型の分岐が出尽くした後の変数の型として出現します。
以下のコードではtypeof
による条件分岐によって、最後のブロックのarg
にはstring
型の値もnumber
型の値もあてはまらないこととなります。
引数はstring
型かnumber
型であると型注釈が付けられていますので、最後のブロックのarg
は存在し得ない値、つまりnever
型が割り当てられることになります。
function fn(arg: string | number) {
if (typeof arg === "string") {
console.log(arg) //(parameter) arg: string
} else if (typeof arg === "number") {
console.log(arg) //(parameter) arg: number
} else {
console.log(arg) //(parameter) arg: never
}
}
never
型とインターセクション型
インターセクション型とは、型&型
のように表される型で、&
で結ばれたすべての型の条件をみたすような型を表します。集合論でいえば、積集合(「AかつB」)に相当するものですね。
以下のコードでは「squid
プロパティが足りない」とコンパイルエラーが出ています🦑
type Octopus = {octopus: string}
type Squid = {squid: string}
const obj: Octopus & Squid = {octopus:'blue-ringed'}
//Type '{ octopus: string; }' is not assignable to type 'Octopus & Squid'.
//Property 'squid' is missing in type '{ octopus: string; }' but required in type 'Squid'.(2322)
never
型とのインターセクションはすべてnever
型となります。
これは、空集合との積集合(共通部分)が常に空集合となることに対応しています。
type T = number & never //type T = never
また、string
型とnumber
型のように両方の型に合致する値が存在しないような型の組み合わせでインターセクションをとると、こちらも常にnever
型となります。
こちらも共通の要素を持たない集合同士の積集合をとると空集合となることに対応していますね。
type T = number & string //type T = never
never
型を返す関数
常に例外を投げるなど、値を返さない関数の返り値の型としてnever
を使用できます。
例外を投げる関数の返り値を取得することは不可能なため、存在しない値が返ってくるとみなせて、never
型を設定してもコンパイルエラーが起きないわけですね。
function throwError(): never {
throw new Error();
}
const neverValue:never = throwError() //コンパイルエラーは発生しない。
上のコードではthrowError
の返り値にnever
型の型注釈をつけなかった場合にはvoid
型が返ってくると型推論されてしまい、代入部分でコンパイルエラーが発生します。
Type 'void' is not assignable to type 'never'.(2322)
また、never
型を返り値とする関数は、ブロックの最後まで到達できないように記述されている必要があります。ブロックの最後まで到達する可能性がある場合にはコンパイルエラーが出るようになっています。
function throwError(str: string):never {
if(str.length < 20){
throw new Error(str)
}
}
//A function returning 'never' cannot have a reachable end point.(2534)
never
についての公式Docsはこちら↓
-
型アサーション
as never
などで型安全性を破壊すればその限りではないですが。 ↩︎
Discussion