Flow is the JavaScript type checker I have been waiting for
I am very excited about Flow, a new JavaScript type checker from Facebook. I have put some thought into what a type checker for JavaScript should do - and in my opinion Facebook gets it right. The designers of Flow took great effort to make it work well with JavaScript idioms, and with off-the-shelf JavaScript code. The key features that make that possible are type inference and path-sensitive analysis. I think that Flow has the potential to enable sweeping improvements to the code quality of countless web apps and Node apps.
From the announcement post:
…underlying the design of Flow is the assumption that most JavaScript code is implicitly statically typed; even though types may not appear anywhere in the code, they are in the developer’s mind as a way to reason about the correctness of the code. Flow infers those types automatically wherever possible, which means that it can find type errors without needing any changes to the code at all.
This makes Flow fundamentally different than existing JavaScript type systems (such as TypeScript), which make the weaker assumption that most JavaScript code is dynamically typed, and that it is up to the developer to express which code may be amenable to static typing.
Path-sensitive analysis means that Flow reads control flow to narrow types where appropriate.
This example comes from the announcement post:
;
// Type error: x might be null
The presence of a use of length
with a null
argument informs Flow that there
should be a null check in that function.
This version does type-check:
;
Flow is able to infer that x
cannot be null
inside the if
body.
An alternate fix would be to get rid of any invocations of length
where the
argument might be null
.
That would cause Flow to infer a non-nullable type for x
.
This capability goes further - here is an example from the Flow documentation:
;
if o == null
o.length;
The type of o
is initially null
.
But Flow is able to determine that the type of o
changes when o
is
reassigned,
and that its type is definitely string
on the last line.
In addition to null checks, Flow also narrows types when it sees dynamic type checks. This example (which passes the type checker) comes from the documentation:
;
The inferred return type of foo
is string | number
.
That is a type union,
meaning that values returned by foo
might be of type string
or of type
number
.
The typeof
checks in bar
narrow the possible types of x
and y
in the
if
body to just number
.
That means that it is safe to multiply values returned by bar
-
the type checker knows that bar
will always return a number.
A coworker told me that what he would like is support for type-checked structs. That would let him add or remove properties from an object in one part of a program, and be assured that the rest of the program is using the new object format correctly. Struct types work using structural types and type aliases:
// Type error: string is incompatible with number
q.z = 4
// Type error: property `z` not found in object type
// Type error: property y not found in object literal
Notice the type
keyword and type annotation on mkPoint
-
Facebook’s literature is a little misleading,
in that Flow is really a new language that compiles to JavaScript.
But the only differences between Flow and regular JavaScript are the added
syntax for type aliases and type annotations,
and the type-checking step.
Flow can be applied to regular JavaScript code without type annotations.
How well that works will depend on how that code is written. If the JavaScript is cleanly written, you might get a lot of help from Flow without ever needing type annotations. But there are some valid JavaScript idioms that will not type-check (at least not at this time.) For example, optional arguments are not allowed unless you use either Flow’s syntax or ECMAScript 6’s default parameter syntax.
These are early days for Flow. I am optimistic that over time it will only get better at operating on code that was not written with Flow in mind.
There is a side benefit to using the Flow language:
it supports ECMAScript 6,
but compiling Flow programs produces ECMAScript 5 code that can run in most
browsers.
Edit 2016-08-19: Early in Flow’s development, Facebook recommended using their own jsx compiler to process code that used Flow. That is what produced ECMAScript 5 code, as mentioned in the previous paragraph. Currently the best practice is to use Babel with the React preset to process Flow code. You still get the benefit of transpiling ECMAScript 6 code to ECMAScript 5, if you have Babel configured to do that.
One of my favorite features of Flow is that null
and undefined
are not
treated as bottom types.
A value is only allowed to be null
if it has a nullable type.
This makes Flow better at catching null pointer exceptions than almost any other
object-oriented language.
For example:
;
// Type error: null is incompatible with object type
;
// this is ok
;
// this is ok too - Flow infers that o has a nullable type
A type expression ?T
behaves pretty much like T | null | undefined
.
(From what I can tell, null
and undefined
are distinct types in Flow -
but it does not seem to be possible to use them in type annotations at this
time.
void
is allowed, which seems to be an alias for undefined
.)
In the past I have talked to one or two people who said that they would only be interested in type-checked JavaScript if it supported algebraic types. (These are the kind of people I work with :)) That is possible too - here is an example that I wrote:
Thanks to type narrowing, accessing tree.value
passes type-checking in the
if
body where tree instanceof Node
is true.
Without that check, the type checker would not allow accessing properties that
do not exist on both Node
and EmptyTree
.
There are some details in this example that I want to call out:
- The
class
syntax is from ECMAScript 6 - but it is extended in Flow to support type annotations for properties. Tree
is a type alias. It has no runtime representation. None is needed, sinceNode
andEmptyTree
have no shared behavior. And unlike a superclass,Tree
is sealed, meaning that it is not possible to add more subtypes toTree
elsewhere in the codebase.- Flow supports parameterized types (a.k.a. generics, a.k.a. parametric polymorphism).
- You can specify the exact type of function values (in this case, the type of
predicate
). find
might return a value from a tree, or it might returnundefined
. So the return type isT | void
(wherevoid
is the type ofundefined
). Writing?T
would also work.
I have not been sold on prior JavaScript type checkers. They did not seem to “get” JavaScript. Flow is a type checker that I actually plan to use.
Revisions
2016-08-19 —
Edited to note that the current best practice for handling JSX is to use Babel