
Fast and precise type checking for JavaScript - deltux
https://blog.acolyer.org/2017/11/08/fast-and-precise-type-checking-for-javascript/
======
dgreensp
I used both Flow and TypeScript extensively at my last job (coda.io), and
through a series of thoughtful discussions and debates, we chose to migrate to
TypeScript, even though we had already started using Flow in parts of our
codebase.

Microsoft did a really good job putting resources behind TypeScript, making
sure tooling and IDE integrations are good, and generally getting a ton of
momentum going. TypeScript is fast-moving, with bugs fixed and features added
at a high rate, and the quality of discussion on the issue tracker is high.
Flow, in comparison, was not evolving as fast.

Flow touts global inference, which I think means better types when you don't
necessarily have annotations on module boundaries, but I mostly care about
what's possible in a greenfield or well-annotated codebase.

Flow deserves a ton of credit, and Microsoft probably studied it in detail,
but I think the advantages are overblown at this point. Take the soundness
thing. TypeScript added null-strictness a while ago, and recently they
improved function type polymorphism. The reality is that both type systems
have limits and you sometimes need to type something imperfectly or use an
escape valve ("any"). In most cases, however, TypeScript gives you more
sophisticated tools to construct the types you want, so you actually get
better types. Maybe TypeScript lacks a theoretical underpinning for soundness,
but there's no reason it can't continue to get "more and more sound," with it
harder and harder to find good examples of unsoundness.

~~~
hackits
Flow/Typescript both of great but writing from start from scratch both flow
and typescript slow me down with all the hassle of needing to define the types
of parameters and data structures. Typescript being a bit verbose at time too.

When working with another person API then typescript does become a godsend in
helping understand what the bloody hell does this callback parameter are
required.

~~~
dahauns
>When working with another person API then typescript does become a godsend in
helping understand what the bloody hell does this callback parameter are
required.

Plot twist: While you are cursing at the person who wrote that API, you slowly
realize that it was you all along.

~~~
hackits
Agree, typescript does help with that slow realization that its a problem with
your code passing in undefined instead of a object.

------
chmln
Flow capitalizes on its soundness, but its not as useful when the error
messages are so cryptic. We tried adopting Flow at a company I worked at, but
dealing with gibberish errors and the huge meaningless stack traces was a
productivity sink.

We migrated to Typescript. Error messages are more understandable and unlike
flow include line numbers and the column. Add huge number of typings, and ts
definitely wins.

There are many gripes we've had with typescript too, but at least we didn't
spend hours trying to understand error messages.

~~~
hasenj
What is soundness, exactly, and how is Typescript unsound?

It seems like a useless academic term that doesn't work so well in the real
world, maybe?

~~~
alangpierce
Soundness means that if the type system says that a variable has a particular
type, then it definitely has that type at runtime. A sound type system is
always correct, and an unsound type system might be incorrect in some cases.

Here's an example of unsoundness in TypeScript:

[https://www.typescriptlang.org/play/index.html#src=function%...](https://www.typescriptlang.org/play/index.html#src=function%20messUpTheArray\(arr%3A%20Array%3Cstring%20%7C%20number%3E\)%3A%20void%20%7B%0D%0A%20%20%20%20arr.push\(3\)%3B%0D%0A%7D%0D%0A%0D%0Aconst%20strings%3A%20Array%3Cstring%3E%20%3D%20%5B'foo'%2C%20'bar'%5D%3B%0D%0AmessUpTheArray\(strings\)%3B%0D%0A%0D%0Aconst%20s%3A%20string%20%3D%20strings%5B2%5D%3B%0D%0Aconsole.log\(s.toLowerCase\(\)\)%0D%0A)

TypeScript incorrectly (but conveniently) says that Array<string> can be
assigned to Array<string | number>, and you can exploit that to create an "s"
variable that TypeScript thinks is a string but is actually a number.

You can try the same code in Flow and it gives a type error:

[https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEA...](https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEAnUgQwE8AKC8gLkTMqoB50pSYwBzRAD6IwIZACNUpAHwBKJgDc4MACaIA3gChE2xPVIA6HCHQEaAZhkBuDQF8NGiAk6JO3PuiYtqHLj15TEAF5EAG0AcmA4ODCAGkQwsXowgF1rNExcQhJyahpXP3Qre0cwZw8XXz4girdedBCAJlSHJzgAG1R9NrhePP0oOAAZOAB3SQBhCnRUGhkZDSA)

~~~
yladiz
So Flow seem to be complaining that _number_ and _string_ are incompatible
types for the array elements. I attempted to change your _arr.push(3)_
statement to one that is appending a string, and it gave the same error even
though it should not have given an error in that case.

Neither Flow nor TypeScript are correct in this instance. Neither keep track
of the actual array element's value, just the general type of the array, which
means they actually don't know for sure and do their best guess. So in the
Flow example, it complains that the number and string types are incompatible
even though it doesn't know that this specific case is incompatible, just the
general case. In the TypeScript example, it should keep track of the type of
the argument value supplied during the function invocation, not the type for
the argument declaration.

In this case I would argue that even though TypeScript is incorrect, it's
preferable to Flow because Flow doesn't infer that the types are incompatible
from actual usage but theoretical.

~~~
alangpierce
The issue with changing `arr.push(3)` to `arr.push('baz')` is that there's
still a type annotation on the function saying `Array<string | number>`. If
you get rid of the type annotation or change it to `Array<string>`, flow is ok
with it:

[https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEA...](https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEAnUgQwE8AKC8gLkTMqoB50pSYwBzAPgCUTAG5wYAE0QBvAFCIFieqQB0OEOgI0A5ACMKAL22CA3LIC+s2RASdEnbn3RMW1Dlx4DEAXkQBtbWA4OG0AGkQ9em0AXTM0TFxCEnJqGgdPdFMrGzA7Z3sPPh8Cx150PwAmWOtbOAAbVBU6uF40lSg4ABk4AHdUUgBhCnRUGkFBWSA)

Both Flow and TypeScript have good type inference (with Flow's generally being
better, I think) and do pretty well with all type annotations removed, but
that wasn't shown in my example because I explicitly annotated all types.

Note that if you do want/need to give an explicit type annotation for this
sort of thing, Flow provides `$ReadOnlyArray`, where `Array<string>` is
assignable to `$ReadOnlyArray<string | number>`:

[https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEA...](https://flow.org/try/#0GYVwdgxgLglg9mABAWwKYGd0FUAOAVAC1QEEAnUgQwE8AKC8gLkQBIAlVCgEwHkwAbKmUpUAPOiikYYAOaIAPojAhkAI1SkAfAEomANzgxOiAN4AoRBcQQE6OH1QA6PnGl1yAbQAMAXS0BuUwBfU1NrMHFEcUkZdCYhajEJKWkNRABeRHcAcmA4OCyAGkQslXos7wC0TFxCEnJqGijk9H8QsIjYyKSZdK7o6XR3ACYK0Js7R2dXdAcoOAAZOAB3dQBhCnRUGi0tUyA)

It sounds like you're arguing that TypeScript is wrong because it's overly-
permissive, and Flow is wrong because it's overly-strict, which makes sense.
That's probably why people prefer to use the word "sound" to describe Flow
rather than "correct". Every sound type system has cases where you can write
perfectly correct code that would be rejected by the type system (which is
provable because of the halting problem). Opting into a type system always
means that you limit the type of code you can write in exchange for better
automatic verification.

~~~
yladiz
An issue with $ReadOnlyArray is that if you're doing something that does work
on both String and Number types, like using them for some template string,
Flow will complain that the types are not compatible even though in this
instance they are. This is a bigger issue with both TypeScript and Flow, in
that they don't seem to keep enough track of the data in arrays, only the type
of the array, to know if the actual types are valid. If TypeScript/Flow kept
track of the array elements and their usage, this issue wouldn't occur in the
same way.

------
lewisl9029
I'd be much more enthusiastic about using a type system if it was a part of an
official ES-next spec. Right now community efforts around static typing are
divided between two very similar, but incompatible type systems, similar to
the situation we had a few years ago with CommonJS and AMD modules (though the
two module systems are much more dissimmilar than Flow and TypeScript). The
module debate has been been mostly laid to rest with the introduction of the
official ES modules spec (at least from the perspective of the application
developer when it comes to module code they actually write), and I'm hoping an
official type annotation spec can do the same for the JS static type checking
landscape.

Flow has already long-since demonstrated that it's perfectly possible to
introduce useful type annotations to JS code without changing any runtime
behavior whatsoever, and Flow and TypeScript have mostly converged on similar
syntax and semantics when it comes to the type annotations. Given all this,
I'd have thought that the standardization process would be pretty far along by
now. Maybe someone more familiar with these matters can offer some insight on
the seeming lack of progress?

~~~
Vinnl
It feels a little bit like if you'd want a linter to be part of an ES spec. As
in: I appreciate TypeScript for the compile-time guarantees it gives me.

~~~
WorldMaker
A good comparison here is the Python approach: Python 3 made the syntax for
type annotations a first-class part of the language but only to the point of
parsing that syntax and providing an AST, it did not (and at least for now
will not) define "compile time" or "run time" meanings for them.

The immediate benefit to adding Typescript/Flow-like annotations to the ES
spec would be that you could run Typescript files unchanged (without
transpilation) in the browser, even if the Browser didn't do any type checks
for you. You could still use TS or Flow to do the type checks in a separate
process, but you could potentially drop the type-stripping steps in TS or
Babel. The possibility exists that eventually the browser could also start to
enforce basic type checks, but the immediate benefit of slightly faster build
processes with no type-stripping step shouldn't be overlooked.

~~~
Vinnl
That makes sense. I can also imagine not wanting to start too quickly with
that though, to learn what such a syntax would have to support from Flow and
TypeScript.

------
swlkr
I thought this was a new thing, but it turns out it's facebook's flow.
[https://flow.org](https://flow.org)

~~~
spraak
Yes, it would be good to add that to the title

------
zefei
Both flow and typescript are great tools. Typescript has much better tooling
and great IDE integration, using typescript is a no-brainer for new projects.
Flow's soundness does shine though once your codebase (and your team) becomes
very big, as any unsafe cast can be reliably treated as error. I use flow at
work, and so far my experience is that unsafe cast in flow always indicates a
bad design or actual bug.

------
makkesk8
I've always wondered if you could let the engine do the type checking or
create type definition files for you, maybe an optional v8 flag?

This would be best case scenario so we could get rid of the tedious work that
one has to put in to make an older library have type checking.

~~~
WorldMaker
Typescript has inference for "plain" JS files if you use the `--allowJS` flag.

That inference is also made available more directly in dts-gen [1], which is a
tool built around Typescript inferencing that produces decent first pass type
definition files for a library.

dts-gen may be something to try the next time you need a definition file.
(Personally, I still prefer to start a new definition file by hand, but it's
good to have an automated option, even if only to double-check your work.)

[1] [https://github.com/Microsoft/dts-gen](https://github.com/Microsoft/dts-
gen)

------
nl
I think the-morning-paper is one of the best things on the internet. It's
probably the thing I miss a proper blog reader for most.

