Skip to content

sitr.us

Advanced features in Flow

Updated 2015-06-08 (originally posted )Flow6 min read

Flow has some very interesting features that are currently not documented. It is likely that the reason for missing documentation is that these features are still experimental. Caveat emptor.

I took a stroll through the source code for Flow v0.11. Here is what I found while reading type_inference_js.ml and react.js.

Edit: It has been pointed out to me that Flow features prefixed with $ are not technically public, and that the semantics of those features may change. But they are useful enough that I plan to use some of them anyway :)

Table of Contents

Class<T>

Type of the class whose instances are of type T. This lets you pass around classes as first-class values - with proper type checking.

I use this in an event dispatch system where events are class instances, and event handlers are invoked based on whether they accept a given event type:

This gives me a compile-time guarantee that event handlers can handle events of the type they are invoked with.

$Diff<A,B>

If A and B are object types, $Diff<A,B> is the type of objects that have properties defined in A, but not in B. Properties that are defined in both A and B are allowed too.

React uses this to throw type errors if components are not given required props, but to leave props with default values as optional.

Where P is the type for component props, and D is the type for default props.

Here is a simplified example:

In my testing, this worked equally well if $Diff was used directly in the type of P instead of as a type bound. But in the React type declarations, it is used in a type bound.

$Shape<T>

Matches the shape of T. React uses $Shape in the signatures for setProps and setState.

Where P is the type of a component's props, and S is the type of a component's state.

An object of type $Shape<T> does not have to have all of the properties that type T defines. But the types of the properties that it does have must match the types of the same properties in T.

In React this means that you can use, e.g., setState to set some state properties, while leaving others unspecified. Note how the type of state in replaceState differs: when calling replaceState you must include a value for every property in S.

Some examples:

An object of type $Shape<T> is not allowed to have properties that are not part of T.

$Shape is like a supertype for objects. Consider that a type { foo: number } (when used as a type bound) represents all objects that have a foo property of type number. That includes objects that have additional properties, such as { foo: 1, bar: 'string' }. So { foo: number } is a supertype of, e.g., { foo: number, bar: string }. The type $Shape<{ foo: number, bar: string }> allows values of type { foo: number }. That is to say, $Shape<T> refers to types that are more general than T - i.e., supertypes.

$Record<T>

Update: $Record<T> is gone in Flow v0.12. But instead of $Record<T>, you can use the nearly equivalent construct: {[key: $Enum<T>]: U}, where U is the type of values in the record.

The type of objects whose keys are those of T. This means that an object of type $Record<T> must have properties with all of the same names as the properties in T - but the types assigned to those properties may be different.

React uses $Record to check the type of propTypes. Here is a simplified example:

Note that Flow does not verify that React.PropTypes.number matches the type number. It just checks that propTypes1 has all of the same keys.

A $Record type might have additional keys that are not included in the original object type.

An important detail to note is that a $Record type can only use property names that are known statically. Dynamic lookups are not allowed.

In its real definition, React actually uses $Record in combination with $Supertype so that you do not get an error if you omit some properties from propTypes.

$Supertype<T>

A type that is a supertype of T. React uses $Supertype in the type of propTypes:

Where the type variable P is the type of the props for a component.

It looks like the intent is to check that if a component defines propTypes, all of the properties listed are also in the type of props. But it should not report an error if propTypes excludes some props. I could not get a type error when testing this. It could be that interaction between $Supertype and object types is still a work in progress.

If you are thinking of using $Supertype with an object type, consider $Shape - it might be a better choice.

$Subtype<T>

A type that is a subtype of T. This is what you get when you use a type bound. For example, these signatures are equivalent:

But the second version has the disadvantage that you cannot refer to the type that obj gets in the return type of the function, or in the types of other arguments.

While trying to come up with example cases for $Subtype, I came across other nice improvements to Flow that render $Subtype unnecessary in a lot of cases. In a previous version of Flow, I recall (possibly incorrectly) having to use a type bound in a function that takes on object with certain required properties, where you don't want to prevent the caller from including additional properties. But now this works without a type bound:

I also had a thought that $Subtype could be used to implement a composition pattern that previously did not work.

But in Flow v0.11 this does work. Hooray!

$Subtype could be useful if you want to define an object type that can be assigned properties that are not declared in the type:

However this weakens property checking on the extensible type. For example, Flow does not infer that the type of a.foo must be number. (It checks the type of foo correctly when the object is first created, but not on reads or reassignment).

$Enum<T>

The set of keys of T. One use for this is to write a lookup function, and have Flow check that the lookup key is valid.

The type $Enum<typeof props> is a lot like this type:

But with $Enum, Flow computes the type union for you.

Flow's tests include more examples.

Existential types

Flow supports an "existential type": *. When * is given as a type, it acts as a placeholder, leaving it to the type checker to infer the type for that position.

Let's generalize the getProp function from the section above. Let's write a function that takes any object and returns a getter. The getter can be used to get arbitrary properties out of the object, without having to refer to the object itself after creating the getter. We would like to write this:

The use of $Enum<T> ensures that a getter can only be called to get properties that actually exist on the given object.

But Flow objects to the lack of type declarations in the inner function. It reports an error:

An obvious idea is to copy types from the return type of makeGetter into the signature of the inner function. But that leads to another problem.

This time the error is:

The problem is that type variables in a function signature (in this case, in the signature of makeGetter) are not in scope in the function body. So we cannot refer to the type T in inner function types, or in variable types in the body of makeGetter.

On the other hand, Flow might be smart enough to figure out what the argument type in the inner function should be. So we can use * to kick the problem over to Flow.

Now we can see makeGetter in action.

Another option would have been to use any instead of *. But that is so much less elegant! The important difference is that where * appears, Flow will fill in a specific type, which could lead to accurate type checking in other areas of the code where the value of type * appears. If you annotate a value with any, Flow will not attempt to type-check expressions where that value appears - which could lead to type errors being missed.

In this case the choice of * versus any does not matter, since the outer function has the type signature that we want.

React uses an existential type to define the ReactClass type:

This allows React to pass around a polymorphic class as a first-class value without losing type information.

Scoped type variables in classes

I mentioned in the last section that type variables in a function's signature are not in scope in the body of that function. I find it interesting that classes do not have this restriction: type variables in a class declaration are in scope in the class definition. (They are not in scope within method bodies; but you can use these variables in method signatures and class variable types). Because of this, there are some problems that do not exactly work with functions but that do work with classes.

We can reimplement makeGetter from the last section as a class.

This probably seems unremarkable - every object-oriented language with static type-checking scopes class-level type variables this way. But ES6 classes are mostly syntactic sugar for ES5 constructor functions - yet a straight translation of Getter to ES5 syntax does not work:

We get the same error: identifier T, could not resolve name. This time to fix the problem we have to refer to T indirectly using typeof obj.

The ability of classes to scope type variables over inner methods, and the ability to use instanceof for type refinement, lead me to use classes more often than I would if I were not using Flow.

Revision history

2015-06-01
Added notice that `$` features are not public.
2015-06-08
`$Record` is gone in Flow v0.12.