質問: hoge==false ? 123 : 456; ``` このコードのように変数の宣言と代入を別のところで行うと`hoge==false`の部分がエラーに なりません。 この違いはどうして起こるのでしょうか?
回答: 投稿の内容に誤りと思われる箇所がありましたので訂正します。また、mjyさんによる回答を推薦します。以下参考までにご覧いただければ幸いです。 basarat/typescript-bookのTypeScript's Type System > Literal Typesが参考になるかと思われます。 TypeScriptにおける*Literal Types*とは、雑に言えば「リテラルとして存在する値**固有**の型」のようなものです。たとえば、`true`という**リテラル**は*literal true型*の値`true`です。あるいは、`false`という**リテラル**は*literal false型*の値`false`です。 ``` true === (true as true); false === (false as false); ``` literal true/false型は明らかにprimitive Booleanの派生型ですが、両者の比較に際してはそれぞれがtrueまたはfalseしかとらない固有の型であるために、異なる型を比較したような状態となり型エラーとなります。 このような比較の例として、特にBoolean,Number,String型のリテラルどうしの比較は(意味があるかはともかくとして)同様の理由によりTypeScriptでは許容されません。 ``` true === false; // (true as true) === (false as false); 1 === 2; // (1 as 1) === (2 as 2); 'foo' === 'bar'; // ('foo' as foo) === ('bar' as bar); // incompatible types ``` では質問のコードですが、 ``` hoge==false ? ... ``` において、比較演算子`==`の両辺の値がどのような型として扱われるかが問題です。 右辺`false`は先述のとおりリテラル固有のliteral false型として解釈されます。 一方左辺`hoge`は定義式に基づくとprimitive Boolean型です。もしこのとおりに解釈されたのであれば、派生型であるliteral false型との比較は可能であり、エラーはでません。 ``` // もしhogeがboolean型であるとされた場合 hoge==false (hoge as boolean) == ((false as false) as boolean) // 派生型のためキャスト(比較)可 ^^^^^^^^^ ``` 一方で、質問のいずれのコードについても、処理を追えば比較部分に到達する時点で変数`hoge`の値は100%の確率で、つまり静的に`true`であることがわかります。これを考慮すればhogeの型も静的に決定可能であり、具体的にはliteral true型です。TypeScriptの型システムにおいてこうした解釈がなされるとすれば、言及されているような型エラーが出ることは理解されます。 ``` var hoge:boolean; hoge = true; // ここに到達する時点で変数hogeがliteral true型であることは // 静的に(コードを実行しなくても)解析可能であるので、 // boolean型と定義されているものの(より厳密に)literal true型として扱われる hoge==false ? ... // (hoge as true) === (false as false) // ^^^^^^^ 比較不能 ``` こうした型を決定する静的解析システムは、TypeScriptにおいてはCFAとよばれています。 反対の例を挙げれば理解が深まるかもしれません。たとえばCFAが「実質的に」primitive Boolean型であるとみなしliteral true/false型として扱わないケースは次のコードです: ``` let hoge: boolean; // 次のコードは結果を静的に解析できない // => 「実質的に」boolean型として扱われる hoge = (Math.random() > 0.5); // 変数hogeはboolean型として解釈され、エラーは出ない hoge === false ? ... ``` こうした取り組み、すなわち「より厳密に」「より実質的に」型をチェックすることで、native jsでは膨大な検証コードを関数の冒頭に必要としたような状況をTypeScriptは解消しています。 上に上げたようなふるまいの是非など、この手の話題はissueを覗くとよく議論されている(はず)ですので、興味があればご確認ください。 この存在価値がいまいちな*literal types*ですが、よりプリミティブな実装の例としては*typeof operator*の返り値の型がそうです。次のコードはこの質問のエラーとまったく同様のエラーになります: ``` typeof hoge === 'nantokakanntoka' // Operator '===' cannot be applied to types '"string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"' and '"nantokakanntoka"'. ``` これは、typeofの返り値が内部で*literal "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"型*と定義されているからです。便宜上typeofを関数のように書けば、次のような疑似コードで定義されているものと考えてください。 ``` function typeof(obj: any): "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" { // ... } ``` この定義のありがたさとしては、native jsがtypeofの返り値をprimitive String型としているためにまったく不正な型名`nantokakanntoka`との比較を許容してしまうことに対して、typescriptではliteral typesを用いてenumのような型付けを簡単に実装できてしまい、かつ型名をいちいち名前をつけて定義していないところです。 このようにliteral typesは「もうちょっとその型のなかでも値を制限したいけど型名をつくるのは大げさだしreturn typeに書きたいし、」みたいなところで活躍するものと(主観では)思います。 以上、個人の理解に基づく内容ですので、誤り等あればご指摘ください。