Skip to content

Comparison with Facebook Flow Type System #1265

Closed
@fdecampredon

Description

@fdecampredon

Disclaimer: This issue has not for purpose to prove that flow is better or worse than TypeScript, I don't want to criticize the amazing works of both team, but to list the differences in Flow and TypeScript type system and try to evaluate which feature could improve TypeScript.

Also I won't speak about missing features in Flow since the purpose is as stated to improve TypeScript.
Finally this topic is only about type system and not about supported es6/es7 features.

mixed and any

From the flow doc :

  • mixed: the "supertype" of all types. Any type can flow into a mixed.
  • any: the "dynamic" type. Any type can flow into any, and vice-versa

Basically that's mean that with flow any is the equivalent of TypeScript any and mixed is the equivalent of TypeScript {}.

The Object type with flow

From flow doc :

Use mixed to annotate a location that can take anything, but do not use Object instead! It is confusing to view everything as an object, and if by any chance you do mean "any object", there is a better way to specify that, just as there is a way to specify "any function".

With TypeScript Object is the equivalent of {} and accept any type, with Flow Object is the equivalent of {} but is different than mixed, it will only accepts Object (and not other primitive types like string, number, boolean, or function).

function logObjectKeys(object: Object): void {
  Object.keys(object).forEach(function (key) {
    console.log(key);
  });
}
logObjectKeys({ foo: 'bar' }); // valid with TypeScript and Flow
logObjectKeys(3); // valid with TypeScript, Error with flow

In this example the parameter of logObjectKeys is tagged with type Object, for TypeScript that is the equivalent of {} and so it will accept any type, like a number in the case of the second call logObjectKeys(3).
With Flow other primitive types are not compatible with Object and so the type-checker will report and error with the second call logObjectKeys(3) : number is incompatible with Object.

Type are non-null

From flow doc :

In JavaScript, null implicitly converts to all the primitive types; it is also a valid inhabitant of any object type.
In contrast, Flow considers null to be a distinct value that is not part of any other type.

see Flow doc section

Since the flow doc is pretty complete I won't describe this feature in details, just keep in mind that it's forcing developer to have every variables to be initialized, or marked as nullable, examples :

var test: string; // error undefined is not compatible with `string`
var test: ?string;
function getLength() {
  return test.length // error Property length cannot be initialized possibly null or undefined value
}

However like for TypeScript type guard feature, flow understand non-null check:

var test: ?string;
function getLength() {
  if (test == null) {
    return 0;
  } else {
    return test.length; // no error
  }
}

function getLength2() {
  if (test == null) {
    test = '';
  }
  return test.length; // no error
}

Intersection Type

see Flow doc section
see Correspondin TypeScript issue #1256

Like TypeScript flow support union types, it also support a new way of combining types : Intersection Types.
With object, intersection types is like declaring a mixins :

type A = { foo: string; };
type B = { bar : string; };
type AB = A & B;

AB has for type { foo: string; bar : string;};

For functions it is equivalent of declaring overload :

type A = () => void & (t: string) => void
var func : A;

is equivalent to :

interface A {
  (): void;
  (t: string): void;
}
var func: A

Generic resolution capture

Consider the following TypeScript example:

declare function promisify<A,B>(func: (a: A) => B):   (a: A) => Promise<B>;
declare function identity<A>(a: A):  A;

var promisifiedIdentity = promisify(identity);

With TypeScript promisifiedIdentity will have for type:

(a: {}) => Promise<{}>`.

With flow promisifiedIdentity will have for type:

<A>(a: A) => Promise<A>

Type inference

Flow in general try to infer more type than TypeScript.

Inference of parameters

Let's give a look at this example :

function logLength(obj) {
  console.log(obj.length);
}
logLength({length: 'hello'});
logLength([]);
logLength("hey");
logLength(3);

With TypeScript, no errors are reported, with flow the last call of logLength will result in an error because number does not have a length property.

Inferred type changes with usage

With flow unless you expressly type your variable, the type of this variable will change with the usage of this variable :

var x = "5"; // x is inferred as string
console.log(x.length); // ok x is a string and so has a length property
x = 5; // Inferred type is updated to `number`
x *= 5; // valid since x is now a number

In this example x has initially string type, but when assigned to a number the type has been changed to number.
With typescript the assignation x = 5 would result in an error since x was previously assigned to string and its type cannot change.

Inference of Union types

Another difference is that Flow propagates type inference backwards to broaden the inferred type into a type union. This example is from facebook/flow#67 (comment)

class A { x: string; }
class B extends A { y: number; }
class C extends A { z: number; }

function foo() {
    var a = new B();
    if (true) a = new C(); // TypeScript reports an error, because a's type is already too narrow
    a.x; // Flow reports no error, because a's type is correctly inferred to be B | C
}

("correctly" is from the original post.)
Since flow detected that the a variable could have B type or C type depending of a conditional statement, it is now inferred to B | C, and so the statement a.x does not result in an error since both types has an x property, if we would have tried to access the z property and error would have been raised.

This means the following will compile too.

var x = "5"; // x is inferred as string
if ( true) { x = 5; } // Inferred type is updated to string | number
x.toString(); // Compiles
x += 5; // Compiles. Addition is defined for both string and number after all, although the result is very different

Edit

  • Updated the mixed and any section, since mixed is the equivalent of {} there is no need for example.
  • Added section for the Object type.
  • Added section on type inference

Feel free to notify if I forgot something I'll try to update the issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions