flowtypeの実現する実行時例外のない世界

今日こんなスライドを見かけた。

djcordhose.github.io

これは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 optionOCamlの提供するOptionという型で、int型かもしくは値がない、ということを示している。
この挙動を知っていれば、先ほどの結果にも素直に頷ける。

スライドにも出てくるけど、typescriptも2.0でNon-nullable typeに対応するらしい。期待。
https://github.com/Microsoft/TypeScript/pull/7140

もうマージはされていて、最新repositoryで strictNullCheckstarget es6 を有効にすれば動くらしい。
今度試してみよう。