Description
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
andany
section, sincemixed
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.