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:
With flow promisifiedIdentity will have for type:
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.
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.
mixedandanyFrom the flow doc :
Basically that's mean that with flow
anyis the equivalent of TypeScriptanyandmixedis the equivalent of TypeScript{}.The
Objecttype with flowFrom flow doc :
With TypeScript
Objectis the equivalent of{}and accept any type, with FlowObjectis the equivalent of{}but is different thanmixed, it will only accepts Object (and not other primitive types likestring,number,boolean, orfunction).In this example the parameter of
logObjectKeysis tagged with typeObject, for TypeScript that is the equivalent of{}and so it will accept any type, like anumberin the case of the second calllogObjectKeys(3).With Flow other primitive types are not compatible with
Objectand so the type-checker will report and error with the second calllogObjectKeys(3): number is incompatible with Object.Type are non-null
From flow doc :
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 :
However like for TypeScript type guard feature, flow understand non-null check:
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 :
AB has for type
{ foo: string; bar : string;};For functions it is equivalent of declaring overload :
is equivalent to :
Generic resolution capture
Consider the following TypeScript example:
With TypeScript
promisifiedIdentitywill have for type:With flow
promisifiedIdentitywill have for type:Type inference
Flow in general try to infer more type than TypeScript.
Inference of parameters
Let's give a look at this example :
With TypeScript, no errors are reported, with flow the last call of
logLengthwill result in an error becausenumberdoes not have alengthproperty.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 :
In this example x has initially
stringtype, but when assigned to a number the type has been changed tonumber.With typescript the assignation
x = 5would result in an error sincexwas previously assigned tostringand 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)
("correctly" is from the original post.)
Since flow detected that the
avariable could haveBtype orCtype depending of a conditional statement, it is now inferred toB | C, and so the statementa.xdoes not result in an error since both types has anxproperty, if we would have tried to access thezproperty and error would have been raised.This means the following will compile too.
Edit
mixedandanysection, sincemixedis the equivalent of{}there is no need for example.Objecttype.Feel free to notify if I forgot something I'll try to update the issue.