Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some mistakes #1

Open
vkurchatkin opened this issue Dec 23, 2016 · 9 comments
Open

Some mistakes #1

vkurchatkin opened this issue Dec 23, 2016 · 9 comments

Comments

@vkurchatkin
Copy link

Some mistakes I found:

type safety can't be great for both. If Flow is great then TypeScript should be good.

Flow has never type, but it's called empty.

(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).

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 TypeScript equivalent should be: type mixed = string | number | boolean

This is incorrect, mixed is a supertype of all types, not just primitives. The closest thing in TypeScript is type mixed = {};.

Lookup Types

In Flow:

type A = {
  thing: string
}


type lookedUpThing = $PropertyType<A, 'thing'>

Read-only Types

In Flow:

type A = {
   +b: string
}

let a: A = { b: 'something' }
@niieani
Copy link
Owner

niieani commented Dec 23, 2016

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 be good.

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!

@niieani
Copy link
Owner

niieani commented Dec 23, 2016

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 a name 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 a nname property on it!

If you believe this is a mistake in their docs, please open an issue in the Flow repository.

@vkurchatkin
Copy link
Author

vkurchatkin commented Dec 23, 2016

(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.

@vkurchatkin
Copy link
Author

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.

@niieani niieani reopened this Dec 23, 2016
@basarat
Copy link

basarat commented Dec 26, 2016

TypeScript 2.0 didn't change too much, really.

@vkurchatkin going through your examples:

  1. https://github.com/vkurchatkin/typescript-vs-flow/blob/master/ts/1.ts also fixed with strictNullChecks
  2. https://github.com/vkurchatkin/typescript-vs-flow/blob/master/ts/2.ts also fixed
  3. https://github.com/vkurchatkin/typescript-vs-flow/blob/master/ts/3.ts also errors in ts
  4. https://github.com/vkurchatkin/typescript-vs-flow/blob/master/ts/4.ts if you copy the flow example https://github.com/vkurchatkin/typescript-vs-flow/blob/master/flow/4.js and paste into a ts file, it also errors.
    image

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):

image

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 🎅 🌹

@vkurchatkin
Copy link
Author

vkurchatkin commented Dec 27, 2016

@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.

@niieani
Copy link
Owner

niieani commented Dec 27, 2016

@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 🎅 !

@vkurchatkin
Copy link
Author

Here are the most notable examples:

  • arrays are covariant:
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
  • objects are covariant with respect to property type
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
  • objects with optional property is considered subtype of object without a property
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:
function test(x: string) {
    x.toLowerCase(); // runtime error
}

const a: string | number = 1;
const fn: (x: string | number) => void = test;

fn(a);
  • type refinements (aka flow control analysis) ignore actual type system:

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');
  • type refinements (aka flow control analysis) are not invalidated:
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
}

@niieani
Copy link
Owner

niieani commented Dec 28, 2016

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants