The Abyss



Go with a Flow

You've heard about Flow, but not sure what it is, how to use it and whether it's worth it? I had to find answers to this questions so hopefully you don't have to. I've started with Flow as a contributor to already an established project with extensive (and ever growing) Flow coverage. It was a stark contrast to how JavaScript's usual treatment of types, in fact it seemed formalistic and unnecessarily specific at times. However, just like with writing tests, well type-annotated code will take care of itself and let you know about a problem before things went wrong.

Why use Flow?

Flow allows strict typing in JavaScript. That is really not at all helpful, so I'll try again. Lets look at the following code.

var foo = 0; //here is a number  
var bar = '0'; //here is a string  
foo == bar //true but why?  
foo === bar //false as it should be  

So why exactly == equates values of two very different types? It happens because of a concept called coercion. When using == we ask JS to compare values but not type so types are converted in case they don't match, === on the other hand compares both values and types. That's what coercion is. Of course we may want values to be converted into different type, that is a so-called explicit coercion.

var wasNumber = 42;  
var nowString = number.toString();  
nowString //'42'  

Let's take a look at implicit coercion in action.

var x = 2; //totally a number  
var y = '2'; //not a number  
van z = x*y;  
z // prints 4... wait what?  

So this kind of things can sneak up on you and they are a real pain to debug. Javascript is dynamically typed language which we can define as a language that will let you know that 'X' is not a function only when the program is running (or runtime). But this is not true for every programming language. Some of the languages check type statically or during compile time, which means before the program is run.

So Flow exists to allow static type checking in Javascript, it checks type as you work on the project. The least invasive way to use Flow is to allow Flow to use type inference (automatic deduction of data type based on language syntax etc). You can also choose to be very specific about each variable, function argument and returned type. This of course depends on the project and your own approach to development.

Static type checking is not inherently better than dynamic one, in fact there are a lot of opinions about those two. You may want to read this post if you still not sure whether you need to learn Flow.

How to start?

Like this \\@flow. Just add this comment at the top of the file after installing and that's it. Use /* @flow weak */ if you only want to be notified about mismatch and don't want to work on type annotation or if you have existing code base, which you want to gradually refactor using Flow.
You can decide between periodically running flow check from console and starting a server, which will watch changes in your source files and will detect any errors and inconsistent type usage.

Syntax Intro

While the flexibility is a strength of Flow, one of it's most obvious weaknesses is far from ideal docs. I refer to this detailed post in case you want an in depth information about flow syntax. For now let's start with js primitive types using a cat as an example.

var name: string = 'Fluffy';  
var age: number = 3;  
var likesDogs: boolean = false;  
var currentThoughts: void = undefined;  
var likesRoomba: null = null;  
var foodPreferences: any = 'tuna';  

First thing you may notice is that despite undefined === null Flow separates those two types. Apart from that this syntax seems easily readable. any type basiacally means Flow will skip checking this variable, which may be a very good thing or a very bad thing, so it should be used with caution.
Now let's move to arrays, which flows describes as types of array elements like Array<type>

var favouriteToys: Array<string> = ['mouse', 'birdie', 'socks', 'human toes'];  
//alternative syntax
var favouriteStarTrekTNGCharacters: string[] = ['Spot'];  

Object types can be defined both very loosely and very detailed.

vat obj: Object = {};  
let cat: {name: string, age: number, likesDogs: boolean} = {name: 'Fluffy', age: 3, likesDogs: false};  
//object can be a registry of all cats in the neighbourhood
let catRating: {[name:string]: number} = {};  
catRating["Fluffy"] = 10;  
//Fluffy is a solid ten

The most interesting and complicated syntax concerns functions.

var aFunction: Function = () => {console.log('called');};  

Function is of course a type in Flow, but Flow is smart enough to find out what is a function by itself. You can annotate function very specifically, including arguments and returning type.

function sum(a: number, b: number): number {  
  return a+b;
}

async function asyncEquate(  
  remoteNumber: Promise<number[]>,
  localNumber: number,
): Promise<boolean> {
  var firstNumber = await remoteNumber;
  return firstNumber === localNumber;
}

Flow allows you to define your own types, and it is the most powerful feature, which can become a way for contributors to easily grasp how things work in you project.

type Cat = {  
  name: string,
  age: number,
  likesDogs: boolean,
};

function addCat(  
  {
   name, age, likesDogs = false,
  }: Cat
): void {
  ...
}

In this example function only accepts this type as an argument and one of the parameters has a default value. Types can be strict this way only object structured certain way would be accepted

type Cat = {|  
  name: string,
  age: number,
  likesDogs: boolean,
|};
//note the use of '|'

addCat({  
  name: 'Spot',
  age: 5,
  likesDogs: true,
  furColor: 'black and white',
});
//This will cause an error

Last cool thing you need to know is how to make Flow more flexible with maybe types and disjointed unions

type Cat = {|  
  name: ?string, //only house cats have names
  age: ?number, //cats rarely remember their birthdays
  likesDogs: boolean,
|};

addCat({  
  name: null,
  age: null,
  likesDogs: false,
});
//maybe values can be either a declared type or a null

Now lets make addCat accept more than one type as an argument.

function addCat(  
  {
   name, age, likesDogs = false,
  }: Cat | Array<Cat> 
): void {
  ...
}

addCat(  
  [{
    name: null, age: null, likesDogs: false,
  }, {
    name: 'Sylvester', age: 1, likesDogs: true,
  }]
);
//'|' (pipe) means more than one type is viable

You can find more detailed examples for classes, mixed types etc in the docs. That's it for now, next I am going to explain how to check how read Flow error and how and why to increase Flow coverage in your project.



Elvina Valieva

Marburg |

Life is more than books, you know, but not much more