4 ms·
Ok, I've used TS while contributing to OSS written with TS and I've used Flow extensively in production settings. The main takeaway for me is that TS is much m
by andreypopp 9y ago
Ok, I've used TS while contributing to OSS written with TS and I've used Flow extensively in production settings.
The main takeaway for me is that TS is much much less sound than Flow. That means it checks less invariants which in turn makes you less trust it...
Consider the following code which is valid (!) in TS:
let promisedString: Promise<string> = new Promise(
resolve => {
// Perfectly valid in TypeScript!
resolve(42)
}
)
Another downside of TS is poor type inference. It requires to annotate each top level function while Flow only requires to annotate exported functions.
Even the following simple piece of code will make TS ask you to annotate doAddition function:
export function addition(a: number, b: number) {
return doAddition(a, b)
}
function doAddition(a, b) {
return a + b
}
I'm really glad Flow doesn't force me into it.
Edit: spelling
- ng12 9y agoFWIW your Promise example has to do with whatever implementation of Promise you're using, not Typescript. Bluebird's typings would correctly complain with that example -- both because your second Promise is implicit-any, and the resolve is wrong.
- andreypopp 9y agoEDIT 2: This still surprises me... let promisedString: Promise<string> = new Promise<string>( resolve => { // Perfectly valid in TypeScript! resolve(undefined) } ) EDIT: no, I was wrong — Bluebird typings won't allow this behaviour but they force me to annotate both the binding and the Promise constructor call: let promisedString: Promise<string> = new Promise<string>( resolve => { resolve(42) // type error here, ok } ) That's surprising TS ships with such unsafe typings for ES2015 Promise API... Hm... no. First, note that this is with noImplicitAny activated. You can check this in TS playground. Then if we inspect what typings TS have for ES2015 Promises[1] and for Bluebird[2], you can clearly see that those are identical in this case.. [1]: https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L33 https://github.com/Microsoft/TypeScript/blob/master/lib/lib.... [2]: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/bluebird/v2/index.d.ts#L34 https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
- ng12 9y agoEr, I'm looking at it right now? I don't know what to tell you. error TS2322: Type 'Bluebird<{}>' is not assignable to type 'Bluebird<string>'. And if I add the type parameter to the constructor... error TS2345: Argument of type '42' is not assignable to parameter of type 'string | Thenable<string>' Re: your edit, you actually don't need that first typing. Typescript will infer the type of the promisedString.
- andreypopp 9y ago> your edit, you actually don't need that first typing. Typescript will infer the type of the promisedString Ok, but still this is confusing, it can't infer the type of RHS given the annotated LHS? Intuitively there's nothing wrong with it and Flow does that well...
- mcintyre1994 9y agoNote this is similar to c# (and thinking about it, every OO language I can think of): var cat = new Animal<Cat>(...); // yup! Animal<Cat> cat = new Animal(...); // Nope I wonder if internally ts is treating these generic-accepting classes in the same way, where the generic is required?
- svieira 9y agoInterestingly enough #1 seems more like another case of #2, because: let promisedString = new Promise<string>( resolve => { // Fails as expected in TypeScript! resolve(42) } )
- andreypopp 9y agoNot sure — TS doesn't report any errors in the original code so I assume it thinks code is fine but it is not. That's totally different than #2.
- Myrannas 9y agoI am not convinced that parameter type inference is a reason to use flow. I have seen examples where flow fails to report errors because it can't infer the parameter types. I would rather consistency over inference that sometimes works sometimes fails silently. For example: function fn(a) { const {b} = a; console.log(b.c); } fn({}); This compiles without errors - but would fail at runtime. https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVUCuA7AxgFwEs5swpsAKAQwEowBvVMXEgZ3wYCMBfMAXjBUA3GCaiwzNnBgBTAHTwA5hU5zcNIam7pyFetw1A https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVUCuA7AxgFwEs5swp...
- andreypopp 9y agoYep, reported to Flow issue tracker.
- xpaulbettsx 9y agoUhhh nope. In TypeScript 2.2.2 your first example is definitely the error you'd expect.
- andreypopp 9y agoTry it in the playground.
- bcherny 9y agoFor your 1st example, it's failing because by explicitly annotating `promisedString` you are widening the type from the inferred `number` to `any`. If you remove the explicit annotation and let TS do its job, the code will fail (as expected) :). For your second example - it's a divisive issue. I believe that a Hindley-Milner style type resolver (what Flow uses) is more usable, but there is a strong argument against it too [0]. [0] https://github.com/Microsoft/TypeScript/issues/15114 https://github.com/Microsoft/TypeScript/issues/15114
- andreypopp 9y ago> by explicitly annotating `promisedString` you are widening the type from the inferred `number` to `any` That's something I don't understand. Isn't that incorrect behaviour? Shouldn't TS try unify RHS and LHS?
- WorldMaker 9y agoTS is trying to unify RHS and LHS. TS follows a somewhat complicated widening process to unify unannotated generics, somewhat leaning towards ease of use over strictness. Adding additional strictness checks like noImplicitAny can help catch these situation. (In this case, it doesn't help, because the resolve argument is inferred `resolve: (value: any) => void` rather than `resolve: any` which would give a clear implicit any error. Inferred any in generic callbacks sound like a good strictness test to add...) In this case in Typescript it is generally more strict to add a type annotation to the generic than the LHS: let promise = new Promise<string>((resolve) => resolve(42)) // This won't work This is as much because the inference here that matters isn't between LHS and RHS but between generic arguments and function return inference.
- josteink 9y ago> The main takeaway for me is that TS is much much less sound than Flow And in personal anecdote-space I've experience the exact opposite :)
- andreypopp 9y agoWell, I've provided examples where TS demonstrates unsound behaviour while Flow does well. What's your examples from personal anecdote-space?
- josteink 9y agoI've tested your example and sure: That seems like a bug in the declaration of the Promise-class. What I like about TypeScript and which I think is "sound" compared to Flow is the whole experience and how it is backed by good tooling. With TypeScript I can open almost "any" editor and expect it to understand and respect the typings of all my TypeScript code. It will correctly autocomplete, and tell me when I'm using undefined members. Basically with TypeScript every part of the stack seems to work for me, the developer, to ensure I make the right decisions and get the assistance I need, at all time. That makes it feel like a very safe and sound language to write code in. With flow? Well fuck that. From what I can tell, you get almost nothing. You can run your compiler and see if it validates, but I've seen almost no editor-support worth mentioning, meaning that while the type-inference in the compiler may occasionally be better (but for how long? TS has a ton of momentum), the overall experience feels much less sound and much more fragile. Much more unsafe. The type-declarations definitely feel as important as the way they are declared: they're mere comments. They're not real.
- 2bit 9y agoLooks like the Promise example will be fixed in TypeScript 2.3.1 https://github.com/Microsoft/TypeScript/issues/14770 https://github.com/Microsoft/TypeScript/issues/14770