Hacker News new | ask | show | jobs
by LegNeato 2925 days ago
> (I'm not aware of any cases where it's been done at this scale.)

Facebook has done it twice at this scale: PHP -> Hack, plain JS -> Flow JS.

2 comments

As far as I know, both Hack and Flow are unsound and don't do any runtime type checks to preserve soundness. It's a lot easier to migrate dynamic code to a static type system if you have the luxury of just ignoring the type system when you want to. :)

In Dart 2, the type system is sound and checked at runtime in cases where it can be proven statically safe (downcasts, variance, etc.). That makes it a lot more work to migrate because the code actually needs to run correctly without violating any of the dynamic type tests.

The Flow core type system is sound: https://dl.acm.org/citation.cfm?doid=3152284.3133872, although there are unfortunately some known unsoundnesses in the actual type system. Does Dart suffer from the same?

Hack does check type hints at runtime, so it's "sound" in the same sense as Java might be. Is this the same sense in which Dart is sound?

> the type system is sound and checked at runtime in cases where it can be proven statically safe

I didn't understand this. If the code is statically proven safe, why would you need runtime checks?

Neither Hack nor Flow are reified, so it's a different matter.

Both of these languages started out with strict null-checking — Hack because there were too many bizarre falsy values in PHP to allow otherwise, and Flow because it worked out so well in Hack. So neither had to tack on strict null-checking afterwards.

There have been some similar large-scale migration efforts. For example, Hack's record types were built on top of PHP arrays, which don't distinguish between absent and null values. Consequently, Hack's record types didn't distinguish between optional and nullable fields. Furthermore, records support width subtyping by default. This is unsound: you can construct distinct types A and B with A <: B <: A.

After a lot of effort, we did migrate the entire codebase to resolve this unsoundness. Essentially, we changed every record declaration to be an "open" record type and made all new records "closed" by default (referring to whether they supported width subtyping).

The analogue, then, would be something like refactoring every type declaration in every consumer's Dart code to be annotated as nullable, adding explicit null-checks, and allowing them to write non-nullable types as the default thenceforth.