4 ms·
And for good reasons. flow started as "the better one of the two in theory" with better ideas, more strictness, way more expresiveness in its type system. But
by boubiyeah 9y ago
And for good reasons.
flow started as "the better one of the two in theory" with better ideas, more strictness, way more expresiveness in its type system. But typescript took all these ideas, listened to the community and bridged the gap in features really fast, together with more stability, thus making flow pretty much obsolete.
typescript is way more stable. I can take a nightly or RC version of typescript and I know it's going to work better than flow which I tried several times and always let me down. If you don't trust a type system, it's hard to justify using it at all, apart from the documentation it provides to other devs.
These projects are complex, and I think only the typescript team invested enough to make it truly work.
- bad_user 9y ago> If you don't trust a type system, it's hard to justify using it at all, apart from the documentation it provides to other devs. 100% agree, although I have to wonder, why don't you see that Typescript is NOT a type system you can depend on. Fact of the matter is that Typescript has serious soundness issues and will always have. Those problems haven't disappeared. What this translates to in practice is that Typescript annotations are little more than documentation. And it simply doesn't compare with any sane static language where the first line of defense is the compiler.
- Silfen 9y ago> Typescript annotations are little more than documentation This is true at the boundaries of your codebase, but if your application is complex enough, tsc is a life saver. Typescript saves me from bugs literally every day. Not to mention the benefits around tooling and refactoring. Agreed that there are some big footguns with respect to soundness, though.
- resf 9y agoWhat is the practical benefit of soundness? I offer that soundness is a disease that makes programming languages far more complex than they have any right to be. A static type system exists to prevent some classes of bugs and enable code completion. However whenever "soundness" becomes a design goal, it enevitably usurps everything else (because it is an objective binary goal) and leads to languages that cease to value other things like simplicity or directness. A type system can never catch all bugs and reliable programs ultimately depend on a human being understanding and verifying the code. The most important goal - if you value reliable programs - is that the code is intelligible to people.
- wereHamster 9y ago> A type system can never catch all bugs True, but that doesn't mean we shouldn't try to improve the type system. We've already seen large improvements in the past in TypeScript and flow. For example, nullable types were not there in the beginning but were added to the type system because they are immensely useful. Making the type system sound would be another such improvement. If you start with that premise, why not "A type system can never catch all bugs, therefore let's not use it at all"? Sounds stupid, doesn't it? > The most important goal - if you value reliable programs - is that the code is intelligible to people. The code that's running the airplane I'm sitting in, I don't give a fuck whether it's intelligible. Though I do care very much that it has been formally proven to be correct. Having a sound type system is pretty much a prerequisite for formal proofs. Also, intelligible code and soundness are not exclusive. What makes you think that? I can write unreadable looking code in JavaScript and Haskell. And I can write beautiful, readable, concise code in both languages, too.
- zxcmx 9y agoI'm not convinced that formal provability as a goal for a language generally used to write web front-ends makes a lot of sense. The "gaps" in TypeScript's type system are actually escape hatches which allow clean-ish interoperability with the code that people in the real world actually use. Like, dammit, JQuery and all the other filthy things that make web pages go. That code is already written and no one is going to change it to make the TypeScript typings "work". Such code by the way is usually incompletely (or even incompetently!) typed but perfectly servicable in practice.
- bad_user 9y agoReading from the comments here, I believe that people don't realize how bad the unsoundness is in Typescript: class Dog { name: String } class Engine { name: String } function start(ref: Engine) { if (!(ref instanceof Engine)) throw Error("oops") } // Typescript does not complain start(new Dog()) This sample is due to Typescript doing "structural typing". According to its rules, if Engine and Dog both have the "name" property, then they must have the same behaviour as well. This is also inconsistent with the behaviour of classes in Javascript (e.g. see the behaviour of `instanceof`). The biggest problem is that generics are what they call "bivariant", which is lingo for "completely fucked up". Here's a classic gotcha that caught people by surprise with Java's array since forever: class Animal { isAnimal: Boolean = true } class Dog extends Animal { isDog: Boolean = true} class Cat extends Animal { isCat: Boolean = true } const dogs: Dog[] = [ new Dog() ] const animals: Animal[] = dogs animals.push(new Cat) for (const dog of dogs) console.log(dog.isDog) //=> true //=> undefined Well, it's unfair to pick on this, since they give a similar example in their own documentation, right? But this extends to inheritance as well, because input parameters in functions are not contravariant in Typescript: abstract class FlatMap<A> { abstract flatMap<B>(f: (a:A) => FlatMap<B>): FlatMap<B> } class Box<A> extends FlatMap<A> { flatMap<B>(f: (a: A) => Box<B>): Box<B> { throw new Error("Not implemented!") } } It should be obvious to everybody why this is wrong: class AnotherBox<A> extends FlatMap<A> { ... } const box: FlatMap<number> = new Box<number>() box.flatMap(x => new AnotherBox<number>()) In case you're wondering, that interface is the beginning of the Monad pattern and the problem here is that monads are incompatible with each other. So in order to compose instances together, you do rely on the compiler to protect you. Think Promises being combined with Arrays, both of which are monadic types. Not going to work, is it? In less expressive static language lacking higher kinded types, which you need to express such interfaces, there's a current trick that people do to work around it: https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki... ; This has been used in Elm and it's being used in Kotlin as well, see for example: https://github.com/FineCinnamon/Katz https://github.com/FineCinnamon/Katz But Typescript is on a whole new level of wrong, because you can't trust the compiler for correctness, so you can't trust it for protection, at all. You see, this is not pie in the sky academics, but actual code that can happen in your app, mistakes that the compiler should have prevented you from making. In the above case Typescript really is just documentation and just like documentation in many cases, it can be wrong documentation. To make this even more frustrating, Microsoft's other language, C#, does not have the problems that I enumerated. And I know what people say - oh, this is still useful. Well, guess what, writing JSDoc + having Google Closure to validate is actually more useful, while not being non-standard ;-)
- RyanCavanaugh 9y agoNit: TypeScript predated flow by 2 years.