今日こんなスライドを見かけた。
これはtypescriptとflowtypeの違いがよく分かるすごく良いスライド。
このスライドの5ページ目に以下の1文がある。
FLOW SOUNDNESS, NO RUNTIME EXCEPTIONS AS GOAL
スライドに刺激を受けたのと、最近flowtypeを触っていたのもあって、これが何を指しているか書きたくなった。
実行環境
- flow: 0.26.0
- typescript: 1.18.10
Flowtypeのきほん
以下のjavasciptのプログラムをflowにかけてみる。
function foo(x) { return x * 10; } foo('Hello, world!');
$ flow flow.js:5 5: foo('Hello, world!'); ^^^^^^^^^^^^^^^^^^^^ function call 3: return x * 10; ^ string. This type is incompatible with 3: return x * 10; ^^^^^^ number
関数fooは引数xに対して10を掛けた値を返却する。
引数xにはnumber型を期待しているが、実際にはstringを渡しているのでエラーが発生する。
非常に強力な型推論だ。
ちなみにtypescriptではこれはエラーにならない。
(まぁjsのシンタックス上エラーじゃないし)
$ node > null * 10 0
typescriptにおいては以下のように引数xの型を明示的に指定することでこれを検知できる。
function foo(x: number) { return x * 10; } foo('Hello, world!');
これをOCamlで記述してみる。
let foo x = x * 10;; # val foo : int -> int = <fun>
let foo x = x * 10;;
の評価値がval foo : int -> int = <fun>
である。
fooはintを受け取ってintを返す関数である、という評価がされた。
OCamlにおいてもxはint型だと推論されていて、flowtypeはOCaml製だけあって近い挙動を示す。
nullable typeを定義
関数fooに少し手を加えて関数barを定義する。
function bar(x) { if(x < 10) { return null; } return x * 10; } console.log(bar(1) * 10);
関数barは、引数が10以下である時にnullを返す。
これをflowtypeにかけてみる。
$ flow flow.js:8 8: console.log(a * 10); ^ null. This type is incompatible with 8: console.log(a * 10); ^^^^^^ number
barはnullを返す可能性があるが、そのハンドリングをせずに * 10しているため、エラーとなる。
ここでは関数barはnullableなnumberを返す関数となったことになる。
ここでnullのチェックを入れると、このエラーは解消される。
function bar(x) { if(x < 10) { return null; } return x * 10; } const a = bar(1); if (a) { console.log(a * 10); }
$ flow No errors!
この世界において、runtime exceptionが起きる可能性がまた1つ根絶された。
これが冒頭の NO RUNTIME EXCEPTIONS AS GOAL
を端的に表した例だ。
これもOCamlで記述してみると、以下になる。
let bar x = if x < 10 then None else Some(x * 10);; # val bar : int -> int option = <fun>
int option
はOCamlの提供するOptionという型で、int型かもしくは値がない、ということを示している。
この挙動を知っていれば、先ほどの結果にも素直に頷ける。
スライドにも出てくるけど、typescriptも2.0でNon-nullable typeに対応するらしい。期待。
https://github.com/Microsoft/TypeScript/pull/7140
もうマージはされていて、最新repositoryで strictNullChecks
と target es6
を有効にすれば動くらしい。
今度試してみよう。