Thanks for sharing that story! I love that your dream was to be the last line of defense protecting users from bad software. We need more of that, and it's sad that execs at Microsoft and others have made it harder.
Definitely some blame also belongs to the Xorg committer who reviewed and merged those changes (and it looks like that person understands that in retrospect). But the primary responsibility for getting a change right is the author's.
I haven’t seen a lot of discussion here of other existing apps moving to Flutter, which surprises me because my experience and my team’s has been so good — the performance, the quality of the code and documentation, and also our experience working with upstream. So I thought I’d share this here.
Happy to answer any questions people have about Flutter and our experience with it. I also plan to write a more detailed technical blog post in a couple of months.
That is in fact one of the new features in this 9.0 release! There's a new less-dense default, and the previous dense layout remains an option (which for myself I immediately turned on).
We're also planning to give a wider array of options in an upcoming release, including independently setting the line-height and the font size. It's a lot of work to get even two options to both have a reasonable layout throughout the UI, though (among other things, it involved changing a lot of hardcoded values in px to be in relative units), which is why only the two-way switch made it into this week's release.
Flow is not sound. They have the ambition of trying to be sound (which I appreciate), but they've never accomplished it.
I went looking for where on their website they claim to be sound. There's definitely some misleading wording here:
https://flow.org/en/docs/lang/types-and-expressions/#toc-sou...
but if you read the whole section, it ends up also acknowledging that it's not entirely sound.
That's true but misleading: if "any" and "unknown" were the only types, then "any" would be indistinguishable from "unknown" and you'd really have just the one type. Which makes the type system sound because it doesn't say anything.
If your type system has at least two types that aren't the same as each other, then adding "any" makes it unsound right there. The essence of "any" is that it lets you take a value of one type and pretend it's of any other type. Which is to say that "any" is basically the purified form of unsoundness.
Yeah, Flow had the ambition to be sound but has never accomplished it.
If you read the Flow codebase and its Git history, you can see that it's not for lack of trying, either — every couple of years there's an ambitious new engineer with a new plan for how to make it happen. But it's a real tough migration problem — it only works if they can provide a credible, appealing migration path to the other engineers across Facebook/Meta's giant JS codebase. Starting from a language like JS with all the dynamic tricks people use there, that's a tough job.
(And naturally it'd be even harder if they were trying to get any wider community to migrate, outside their own employer.)
Flow doesn't even check that array access is in-bounds, contrast to TypeScript with noUncheckedIndexedAccess on. They're clearly equally willing to make a few trade-offs for developer convenience (a position I entirely agree with FWIW)
Neat example, thanks! I hadn't known TS had that option. Array access was actually exactly the example that came to mind for me in a related discussion:
https://news.ycombinator.com/item?id=41076755
I wonder how widely used that option is. As I said in that other comment, it feels to me like the sort of thing that would produce errors all over the place, and would therefore be a real pain to migrate to. (It'd be just fine if the language semantics were that out-of-bounds array access throws, but that's not the semantics JS has.) I don't have a real empirical sense of that, though.
There's nothing arcane or particularly theoretical about soundness. It means that if you have an expression of some type, and at runtime the expression evaluates to a value, the value will always be of that type.
For example if you have a Java expression of type MyClass, and it gets evaluated, then it must either throw (so that it doesn't produce any value) or produce a value of type MyClass: either an instance of MyClass, or of one of its subclasses, or null. It will never produce an instance of some other class, or an int, or anything else that isn't a valid value for the type MyClass.
In addition to helping human readers reason about the code, a sound type system is a big deal for a compiler: it makes it possible to compile the code AOT to fast native code, without inserting a bunch of runtime checks and dynamic dispatching to handle the fact that inevitably some of the types (but you don't know which) are wrong.
The compiler implications are what motivated the Dart language's developers to migrate from an unsound to a sound type system a few years ago:
https://dart.dev/language/type-system#the-benefits-of-soundn...
so that they could compile Flutter apps AOT. This didn't require anyone to make their code resemble what you'd do in a theorem prover — it just means that, for example, all casts are checked, so that they throw if the value doesn't turn out to have the type the cast wants to return.
TypeScript is unsound because when you have an expression with a type, that tells you nothing at all for sure about what the value of the expression can be — it might be of that type, or it might be anything else. It's still valuable because you can maintain a codebase where the types are mostly accurate, and that's enough to help a lot in reading and maintaining the code.
The key factor is that typescript is not a language, it is a notation system for a completely independent language.
The purpose of typescript is usefully type as much javascript as possible, to do both this and have a sound type system it would require to change javascript.
Definitely to get the most ergonomic programming experience, while also having a sound type system, you'd need to change some of the semantics of the language.
A prime example is that if you index into an array of type `T[]`, JS semantics mean the value you get back could be undefined as well as a `T`. So to describe existing JS semantics in a sound type system, the type would have to be `T | undefined`, which would be a big pain. Alternatively you could make the type `T` and have that be sound, but only if you make the runtime semantics be that an out-of-bounds access throws instead of returning undefined.
When a language's type system is sound, that means that if you have an expression with type "string", then when you run the program the expression's value will only ever be a string and never some other sort of value.
Or stated more abstractly: if an expression has type T, and at runtime the expression evaluates to a value v, then v has type T.
The language can still have runtime errors, like if you try to access an array out of bounds. The key is that such operations have to give an error — like by throwing, so that the expression doesn't evaluate to any value at all — rather than returning a value that doesn't fit the type.
Both TypeScript and Flow are unsound, because an expression with type "string" can always turn out to evaluate to null or a number or an object or anything else. Flow had the ambition to be sound, which is honorable but they never accomplished it. TypeScript announced up front that they didn't care about soundness:
https://www.typescriptlang.org/docs/handbook/type-compatibil...
Soundness is valuable because it makes it possible to look at the types and reason about the program using them. An unsound type-checker like TypeScript or Flow can still be very useful to human readers if most of the types in a codebase are accurate, but you always have to keep that asterisk in the back of your head.
One very concrete consequence of soundness that it makes it possible to compile the code to fast native code. That's what motivated Dart a few years ago to migrate from an unsound type system to a sound one:
https://dart.dev/language/type-system
so that it could AOT-compile Flutter apps for speed.
reply