flow
TypeScript

FlowのドキュメントにあるコードをひたすらTypeScriptのPlaygroundで試す

Flow 0.74
Typescript 2.9

TypeScriptの設定は以下な感じ

image.png

先にまとめ

やってみることによって学びがあった。良かった。

  • FlowのObjectTypeは必要なプロパティがあれば同じ型として使えるがTypeScriptの場合はそうではない。
  • 基本型はだいたい同じだが、mixedがないのとvoidの挙動が違う。
  • Maybe型がない。
  • TypeScriptは型を書かないとanyになるがFlowは推論は推論してくれる

多分、お互いに似たようなことはできるが、実際にやるにはそれなりの経験が必要そうだ。逆をやってみるのも面白そう。
これをやってみた感じからするとFlowのほうが好み。

Stringへの暗黙型変換

https://flow.org/en/docs/types/primitives/#toc-strings

// @flow
"foo" + "foo"; // Works!
"foo" + 42;    // Works!
"foo" + {};    // Error!
"foo" + [];    // Error!

TypeScriptはエラーにならなかった。+演算子を使うときは気をつけて。

Maybe

https://flow.org/en/docs/types/primitives/#toc-maybe-types

// @flow
function acceptsMaybeString(value: ?string) {
  // ...
}

acceptsMaybeString("bar");     // Works!
acceptsMaybeString(undefined); // Works!
acceptsMaybeString(null);      // Works!
acceptsMaybeString();          // Works!

残念ながら未対応。自前で実装するしかなさそう。

Mixed

https://flow.org/en/docs/types/mixed/

// @flow
function stringify(value: mixed) {
  // ...
}

stringify("foo");
stringify(3.14);
stringify(null);
stringify({});

そもそもmixedが存在しない模様。
Union型で代用できそうな気はする。

Variables

Reassigning variables

https://flow.org/en/docs/types/variables/#toc-reassigning-variables

let foo = 42;

if (Math.random()) foo = true;
if (Math.random()) foo = "hello";

let isOneOf: number | boolean | string = foo; // Works!

TypeScriptでは代入した時点で foo が numberと決まってしまう。true や "hello"は代入しようとすると型エラーに。flowは型の再設定が可能で、Union Typeになる。

以下の様に書いてあげるとエラーは消える

let foo: number | boolean | string = 42;

if (Math.random()) foo = true;
if (Math.random()) foo = "hello";

let isOneOf: number | boolean | string = foo; // Works!

Fuctions

https://flow.org/en/docs/types/functions/

// @flow
function concat(a, b) {
  return a + b;
}

concat("foo", "bar"); // Works!
// $ExpectError
concat(true, false);  // Error!

エラーにならなかった。abがanyになるため。

Function this

https://flow.org/en/docs/types/functions/#toc-function-this

function method() {
  return this;
}

var num: number = method.call(42);
// $ExpectError
var str: string = method.call(42);

TypeScriptではエラーにならず。method関数の戻り値がanyになってしまう。
TypeScriptでの解決方法うかばず。:thisは使えなかった。

Predicate Functions

https://flow.org/en/docs/types/functions/#toc-predicate-functions

function truthy(a, b): boolean %checks {
  return !!a && !!b;
}

そもそも %checksなぞTypeScriptにない。
TypeScriptでの解決方法は浮かばず。

Unsealed Object

// @flow
var obj = {};

obj.foo = 1;       // Works!
obj.bar = true;    // Works!
obj.baz = 'three'; // Works!

これも同じようにする方法がわからず。すべてのkeyとその型を書く以外の方法がわからなかった。

Exact Object

https://flow.org/en/docs/types/objects/#toc-exact-object-types

// @flow
var foo: {| foo: string |} = { foo: "Hello", bar: "World!" }; // Error!

TypeScriptには無い文法だが、interfaceを使えばよい。

// @flow
interface Hoge { foo: string }
var foo: Hoge = { foo: "Hello", bar: "World!" }; // Error!

Objects as maps

https://flow.org/en/docs/types/objects/#toc-objects-as-maps

// @flow
var o: { [string]: number } = {};
o["foo"] = 0;
o["bar"] = 1;
var foo: number = o["foo"];

TypeScriptは文法が違う。labelをつけてあげればよい。labelをつけてもFlowでは動作する。

// @flow
var o: { [key: string]: number } = {};
o["foo"] = 0;
o["bar"] = 1;
var foo: number = o["foo"];

Tuple type

https://flow.org/en/docs/types/tuples/

// @flow
let tuple: [number, boolean, string] = [1, true, "three"];

let none: void = tuple[3]; // error

TypeScriptでは別の要因でエラーになる。tuple[3]の利用にはエラーがでない。

// @flow
let tuple: [number, boolean, string] = [1, true, "three"];

let none: string | number | boolean = tuple[3]; // error

これだとエラーにならないので、Tupleというより配列として認識している。

strictly enforced tuple length arity

https://flow.org/en/docs/types/tuples/#toc-strictly-enforced-tuple-length-arity

// @flow
let tuple: [number, number] = [1, 2];
// $ExpectError
let array: Array<number>    = tuple; // Error!

TypeScriptではエラーにならない。tupleではなく配列として扱われるため。

// @flow
let tuple: [number, number] = [1, 2];
tuple.join(', '); // Works!
// $ExpectError
tuple.push(3);    // Error!

これも同様。

Opaque Type Aliases

opaque type ID = string;

TypeScriptは対応していない。がんばれば似たようなことはできるはず。

Interface Types

https://flow.org/en/docs/types/interfaces/

// @flow
class Foo {
  serialize() { return '[Foo]'; }
}

class Bar {
  serialize() { return '[Bar]'; }
}

// $ExpectError
const foo: Foo = new Bar(); // Error!

TypeScriptではエラーにならない。
構造的部分型になるため。Flowでは別の型として扱われる。
根本的な思想の違いと考えられる。

Flowでやるなら共通インターフェイスを用意する

// @flow
interface Base {
  serialize(): string;
}

class Foo {
  serialize() { return '[Foo]'; }
}

class Bar {
  serialize() { return '[Bar]'; }
}

// $ExpectError
const foo: Base = new Bar(); // Error!
foo.serialize();

read only and write only

https://flow.org/en/docs/types/interfaces/#toc-interface-property-variance-read-only-and-write-only

interface MyInterface {
  +covariant: number;     // read-only
  -contravariant: number; // write-only
}

TypeScriptではreadonlyの書き方は違うし、writeonlyはやり方しらない。

Generic Type

https://flow.org/en/docs/types/generics/

// @flow

type IdentityWrapper = {
  func<T>(T): T
}

function identity(value) {
  return value;
}

function genericIdentity<T>(value: T): T {
  return value;
}

// $ExpectError
const bad: IdentityWrapper = { func: identity }; // Error!
const good: IdentityWrapper = { func: genericIdentity }; // Works!

TypeScriptだとエラーにならない。identityの引数はanyになるためと考えられる。

型の制約

// @flow
function logFoo<T: { foo: string }>(obj: T): T {
  console.log(obj.foo); // Works!
  return obj;
}

logFoo({ foo: 'foo', bar: 'bar' });  // Works!
// $ExpectError
logFoo({ bar: 'bar' }); // Error!

TypeScriptだと制約の付け方が違う。
extendsで指定できる

// @flow
function logFoo<T extends { foo: string } >(obj: T): T {
  console.log(obj.foo); // Works!
  return obj;
}

logFoo({ foo: 'foo', bar: 'bar' });  // Works!
// $ExpectError
logFoo({ bar: 'bar' }); // Error!

リテラル値のあるオブジェクトのUnion Type

// @flow
type Success = { success: true, value: boolean };
type Failed  = { success: false, error: string };

type Response = Success | Failed;

function handleResponse(response: Response) {
  if (response.success) {
    var value: boolean = response.value; // Works!
  } else {
    var error: string = response.error; // Works!
  }
}

この辺はオブジェクト型の問題なのでどうしようもない。

以下のようにしてもだめだった。

// @flow
interface Success {
    success: true,
    value: boolean,
}

interface Failed {
    success: false,
    error: string,
}

type Response_ = Success | Failed;

function handleResponse(response: Response_) {
  if (response.success) {
    var value: boolean = response.value; // Works!
  } else {
    var error: string = response.error; // Works!
  }
}

typeof

typeof type syntax

https://flow.org/en/docs/types/typeof/#toc-typeof-type-syntax

// @flow
let num1 = 42;
let num2: typeof num1 = 3.14;     // Works!
// $ExpectError
let num3: typeof num1 = 'world';  // Error!

let bool1 = true;
let bool2: typeof bool1 = false;  // Works!
// $ExpectError
let bool3: typeof bool1 = 42;     // Error!

let str1 = 'hello';
let str2: typeof str1 = 'world'; // Works!
// $ExpectError
let str3: typeof str1 = false;   // Error!

let bool2: typeof bool1 = false; // Works!がエラーになる。 bool1 の型はtrueと認識してしまう。どうしようもなさそう。

Typeof inherits behaviors of other types

https://flow.org/en/docs/types/typeof/#toc-typeof-inherits-behaviors-of-other-types

// @flow
class MyClass {
  method(val: number) { /* ... */ }
}

class YourClass {
  method(val: number) { /* ... */ }
}

// $ExpectError
let test1: typeof MyClass = YourClass; // Error!
let test2: typeof MyClass = MyClass;   // Works!

これまでの傾向からわかってるが一応。TypeScriptではエラーにならない。

Type Cast

https://flow.org/en/docs/types/casting/#toc-type-cast-expression-syntax

(1: number)

TypeScriptだとエラーになった。文法の違い。

1 as number

と書けばいいはず。

Utility Type

TypeScriptは全滅するはず。省略。

Module Types

PlayGroundでは再現する方法がうかばないので、省略