Trying Flow

2016/02/04

I'm interested in the possibility of running type checks on Javascript code.

Proponents of type checking believe that bugs can be avoided by indicating the intended type (i.e. String, Number) of values (variables, functions and classes), opponents say it adds work but doesn't significantly reduce bugginess.

The most popular type system for the Javascript ecosystem is Typescript.

An alternative is Facebook's flow, it allows gradual typing - you add only as many type annotations as you want. Here, I'm giving it a spin by creating flow-hello - a "Hello World!" Example.

Installation

The type checking system is written in OCaml, and you need to install a binary or compile from source.

I opted to use `npm` to install the binary globally:

$ sudo npm install flow-bin --global

Project setup

Configure flow:

$ flow init

A Bug

Here's a Javascript with a bug:

function logLength(x) {
  console.log(x.length);
}

logLength("Hello");
logLength(1);

This code logs `undefined` when the number `1` is passed to the function logLength, as 1 does not have a length attribute:

$ node src/01-buggy.js
5
undefined

As is, flow does not analyze the file:

$ flow check
Found 0 errors

Activate flow

Flow is activated by adding @flow to the first comment in any file:

/* @flow */

function logLength(x) {
  console.log(x.length);
}

logLength("Hello");
logLength(1);
$ flow check
src/02-with-flow.js:3
3:   console.log(x.length);
                   ^^^^^^ property 'length'. Property not found in
3:   console.log(x.length);
                 ^ Number

Found 1 error

That's good, as it explains how we get undefined in the output, but it's not clear that this happens due to the second invocation of the function.

Annotate function parameters

Now, let's indicate the intended type of the x parameter so that calls with parameters of the wrong type will be pointed out.

/* @flow */

function logLength(x: string) {
  console.log(x.length);
}
logLength("Hello");
logLength(1);

As flow check checks all the `.js` files it finds, I'll run pass the file contents to flow via stdin:

$ flow check-contents < src/03-parameter-annotations.js
7: logLength(1);
   ^^^^^^^^^^^^ function call
7: logLength(1);
             ^ number. This type is incompatible with
2: function logLength(x: string) {
                         ^^^^^^ string
Found 1 error

That's good - we know which call caused the problem.

Stripping type annotations with Babel

Unfortunately, we can no longer simply run the code:

$ node src/03-parameter-annotations.js
function logLength(x: string) {
                    ^
SyntaxError: Unexpected token :
...

We can use Babel to remove type annotations when we want to run the code.

Setting up Babel requires a bit of work:

In package.json:

{
  "scripts": {
    "babel": "babel ..."
  }
}
$ npm install --save-dev babel-cli babel-plugin-transform-flow-strip-types

In .babelrc:

{
  "plugins": ["transform-flow-strip-types"]
}

Now we can run babel:

$ node_modules/.bin/babel src/03-parameter-annotations.js

function logLength(x) {
  console.log(x.length);
}

logLength("Hello");
logLength(1);

We get normal Javascript as output.

Conclusion

So, I got what I wanted: help with figuring out an error caused by calling a function with a parameter of the wrong type.

But, it's truly a "Hello World!", there is a whole type specification system yet to learn and try out.

I like the idea of being able to annotate just as much of my code as I like, so I think I'll be using Flow on my next Javascript project.