型と式
JavaScriptには、数値、文字列、ブール値、関数、オブジェクトなど、さまざまな種類の値があります。
11234 as number;2"hi" as string;3true as boolean;4[1, 2] as Array<number>;5({prop: "value"}) as {prop: string};6(function func(s: string) {}) as string => void;
これらの値はさまざまな方法で使用できます。
11 + 2;2"foo" + "bar";3!true;4[1, 2].push(3);5const obj = {prop: "s"};6let value = obj.prop;7obj.prop = "value";8function func(s: string) {}9func("value");
これらのさまざまな式はすべて、値の型とそれらに対して実行される操作の結果である新しい型を作成します。
1let num: number = 1 + 2;2let str: string = "foo" + "bar";
Flowでは、すべての値と式に型があります。
静的に型を特定する
Flowは、すべての式の型を特定する方法が必要です。しかし、コードを実行してそれを理解するだけではいけません。もしそうした場合、コードに問題があれば影響を受けてしまいます。たとえば、無限ループを作成した場合、Flowはそれが永遠に終了するのを待ちます。
代わりに、Flowはコードを実行せずに(静的解析)解析することで値の型を特定できる必要があります。すべての既知の型を処理し、周囲のすべての式がどのような結果になるかを把握し始めます。
たとえば、次の式の結果を把握するには、Flowはまずその値が何であるかを把握する必要があります。
val1 + val2;
値が数値の場合、式は数値になります。値が文字列の場合、式は文字列になります。ここには多くの可能性があるため、Flowは値が何であるかを調べる必要があります。
Flowが各値の正確な型を特定できない場合、Flowはすべての可能な値を特定し、周囲のコードがすべての可能な型で引き続き機能することを確認する必要があります。
健全性と完全性
コードを実行すると、単一の式は限られた値のセットでのみ実行されます。しかし、それでもFlowは*すべての*可能な値をチェックします。このようにして、Flowはチェックしすぎている、つまり有効なコードを*過剰に近似*しています。
すべての可能な値をチェックすることにより、Flowはコードの実行時には実際には発生しないエラーをキャッチする可能性があります。 Flowは、*「健全」*であるためにこれを行います。
型システムでは、**_健全性_**とは、型チェッカーが実行時に*発生する可能性のある*すべての単一エラーをキャッチする機能です。これは、実行時には実際には発生しないエラーをキャッチするという犠牲を払うことになります。
反対に、**_完全性_**とは、型チェッカーが実行時に*発生する*エラーのみをキャッチする機能です。これは、実行時に発生するエラーを見逃してしまうという犠牲を払うことになります。
理想的な世界では、すべての型チェッカーは健全*かつ*完全であるため、実行時に*発生する*すべてのエラーをキャッチします。
Flowは可能な限り健全で完全であるように努めています。しかし、JavaScriptは型システムを中心に設計されていないため、Flowはトレードオフを行う必要がある場合があります。このような場合、Flowは完全性よりも健全性を優先し、コードにバグがないことを保証します。
Flowがうるさすぎて生産性を妨げない限り、健全性は問題ありません。健全性が邪魔になる場合は、Flowは代わりに完全性を優先します。 Flowがこれを行うケースはほんの一握りです。
他の型システムは、エラーを見逃す可能性はあるものの、実際のエラーのみを報告するという完全性を優先します。ユニット/統合テストはこのアプローチの極端な形式です。多くの場合、これは最も複雑なエラーを見逃し、その部分を開発者に任せるという犠牲を払うことになります。