Open vkurchatkin opened 7 years ago
Thanks for this!
I will update the list with your remarks. I have a comment and a question regarding the two problems you've pointed out:
(1 + 1 : number)
is not the same as(1 + 1) as number
. It is 100% safe operation. Flow's equivalent for type assertions is((x:any): number)
.
This example was taken from Flow docs. But I believe you are mistaken about the difference, or at least misunderstood that paragraph was talking about the difference between the syntax of type-casting, not about type assertions. (1 + 1) as number
is also a safe operation in TypeScript. To follow your example, TypeScript's equivalent of the unsafe type assertion would be (x as any as number)
. Otherwise you may only cast compatible types to each other (subsets).
mixed
is a supertype of all types
I'm not sure I understand this. It was described vaguely in the docs. Is it a aupertype of all primitive types, or of all types declared in the whole project, or maybe something else? {}
in TypeScript is a literal empty object, so I don't believe this is what you meant.
If Flow is
great
then TypeScript should begood
.
What specific features do you believe make Flow much better at type safety than TypeScript? I'm only speaking about TypeScript >= 2.1 with all the safety features enabled, I don't care about older versions. I would say Flow is only marginally better, at some very specific edge cases, but for the 95% of use cases they're equally type-safe. But I'd love to be proven wrong.
Thanks again!
One more thing:
By default objects in flow are not strict, whereas in TypeScript they are always strict, unless set as partial.
That's not true, object type has the same semantics in Flow and TypeScript.
The Flow docs say something else about this:
Because
{ name: string }
only means “an object with at least aname
property”, Flow can’t be sure that objects of that type don’t also have other properties. For this reason, Flow won’t error if it sees an access of a property called, say,nname
because there’s no guarantee that the object doesn’t actually have anname
property on it!
If you believe this is a mistake in their docs, please open an issue in the Flow repository.
(1 + 1) as number is also a safe operation in TypeScript.
It's not. You can do this, which is unsafe:
function t(x: string | number): string {
var y = x as string;
return y;
}
Is it a aupertype of all primitive types, or of all types declared in the whole project, or maybe something else? {} in TypeScript is a literal empty object, so I don't believe this is what you meant.
It means that you can assign anything at all to it. AFAIK the same is true for {}
it TypeScript. At least, you can assign primitives to it.
What specific features do you believe make Flow much better at type safety than TypeScript?
Here is a bunch of examples: https://github.com/vkurchatkin/typescript-vs-flow. TypeScript 2.0 didn't change too much, really.
If you believe this is a mistake in their docs, please open an issue in the Flow repository
The last sentence seems like an error, yes. But in general the same is true for TypeScript: object can have any other property.
Here is an example. Given:
type User = { name: string, age: number };
function test(user: User) { }
This is an error in TS, indeed:
const user: User = { name: 'foo', age: 1, nname: '' };
As is this:
test({ name: 'foo', age: 1, nname: '' });
But it's not a type system error, more of a linter-type error. And this is not an error at all:
const user = { name: 'foo', age: 1, nname: '' };
test(user);
The fact that { foo: string }
is a supertype of { foo: string, bar: string }
is a core part of both Flow and TypeScript.
TypeScript 2.0 didn't change too much, really.
@vkurchatkin going through your examples:
strictNullChecks
Thats the top four examples from the repo you presented. As for differences consider the following where flow doesn't error and ts does (calling a function with additional arguments that it doesn't expect):
There are always cases where one handles safety (a fairly objective thought) better than the other, expecially when both are forced to allow things like any
because of how JavaScript developers work.
I wouldn't argue either is technically better than the other. Just the designers have made different choices. PS : Happy holidays 🎅 :rose:
@basarat everything else except 4 is reflected, although it's probably better to remove examples that work in both.
flow doesn't error and ts does
You are right, it's actually an important distinction. Flow is all about type errors. This is not a type error - in both Flow and Typescript. Something like this might indicate a human error indeed, but it's perfectly legal from type system point of view.
I wouldn't argue either is technically better than the other. Just the designers have made different choices
I agree, saying that one is better than the other is pretty meaningless. What I say is that Flow is objectively safer than Typescript, and it's by design. Typescript has other advantages.
@basarat the last example is an important distinction between Flow and TS. Would you consider adding a paragraph in a PR to this repo?
Most of the articles I saw regarding Flowtype vs TS were pretty opinionated and leaned one way or another, I created this repo to make an objective comparison between the two so people can make up their own minds. If any of you spot something that you feel is subjective, please do point it out, I won't bite :).
@vkurchatkin I'd be happy to say Flowtype is a bit safer than TS if we had a comprehensive comparison of those cases in which it is true, and those where it is false (I hate claims not supported by arguments).
Also, does anybody know how to do a code side-by-side view in GitHub's Markdown? That would probably work much better for these examples. Perhaps we'd need to switch the README format to RST or embed HTML?
Happy holidays 🎅 !
Here are the most notable examples:
class Animal {}
class Cat extends Animal {
meow() {}
}
class Dog {}
function test(animals: Animal[]) {
animals.push(new Dog());
}
const cats: Cat[] = [];
test(cats);
cats.forEach(cat => cat.meow()); // runtime error
type A = {
a: string | number
};
type B = {
a: string
};
const b: B = { a: 'foo' };
const a: A = b;
a.a = 4;
b.a.toLowerCase(); // runtime error
type B = { b: number };
type C = { a?: () => void, b: number };
const a = { a: 'foo', b: 1 };
const b: B = a; const c: C = b;
if (c.a) { c.a(); // runtime error }
- functions are bivariant:
```js
function test(x: string) {
x.toLowerCase(); // runtime error
}
const a: string | number = 1;
const fn: (x: string | number) => void = test;
fn(a);
classes are structural but treated as nominal
class Foo {
foo: string;
}
class Bar {
bar: string;
}
function test(x: Foo | Bar) {
if (x instanceof Foo) {
x.foo.toUpperCase()
} else {
x.bar.toUpperCase(); // runtime error
}
}
test({ foo: 'foo' });
Primitives are subtypes of object types, but treated as if they were not:
function test(x: { toLowerCase: () => string } | number) {
if (typeof x === 'object') {
x.toLowerCase();
} else {
x.toFixed(); // runtime error
}
}
test('foo');
class A {
a: string | null;
constructor() {
this.a = 'foo';
}
test() {
this.a = null;
}
}
const a: A = new A();
if (a.a) {
a.test();
a.a.toLowerCase(); // runtime error
}
Great examples @vkurchatkin, thank you. I'll add it to the comparison when I have a moment to format and describe it. I had no idea about some of those, they seem like bugs in TypeScript and shouldn't happen, but do indeed. @basarat do you know if any of the above problems have related issues in the TypeScript repo?
Some mistakes I found:
type safety
can't begreat
for both. If Flow isgreat
then TypeScript should begood
.Flow has
never
type, but it's calledempty
.(1 + 1 : number)
is not the same as(1 + 1) as number
. It is 100% safe operation. Flow's equivalent for type assertions is((x:any): number)
.That's not true, object type has the same semantics in Flow and TypeScript.
This is incorrect,
mixed
is a supertype of all types, not just primitives. The closest thing in TypeScript istype mixed = {};
.In Flow:
In Flow: