L
U
E
T
K
E
M
J
~
C
h
a
n
g
e
~
I
s
~
G
o
o
d
💻

170902

Experiencing Flow

Facebook's static type checker Flow had been on my radar for a while. Typescript left a bad taste in my mouth that was, in all honesty, more likely the fault of a bad combination of an early beta of angular2 and ionic. We don't do typing at work. Certain teammates may or may not experience debilitating java flashbacks at the thought of it.

In order to give flow a proper go I decided to try it on a branch of Aglet Timekeeper. I am in the middle of implementing a new feature and there is, after all, no time like the present. Installation was surprisingly painless. The project already uses babel so I could skip most of the directions on the installation docs. A couple quick atom extensions for linting and the project was ready to go.

One of the nice things about adding flow to an existing project is it is activated on a per file basis. Meaning you can type check individual files instead of your entire project. 👌

First up was a utils file. This bit of code has a few functions used to build the data needed to display the timekeeper. With a fair bit of logic and some chunky math it seemed like a good choice. One of the selling points for flow is it's ability to find errors in your code. This particular file is already heavily unit tested so the chance of showcasing flow's error snooping skills seemed low.

Time to add some static types to the first function:

screen shot 2017-09-05 at 6 33 00 pm

Like so:

screen shot 2017-09-02 at 10 23 15 pm

What the hell? The function returns a string. I set it's type to a string. Is flow broken? This sucks. Flow sucks. Searching the error message resulted in bupkiss. I am confused. What am I missing? I can remove the type definition on the function and the error goes away but that's as bad as removing unit tests so you pass CI.

What is Flow trying to tell me?

...

Aha!

The variable sky is declared but never given a value. The conditional logic has no final else clause so it is possible that sky never becomes a string. This is why the error "possibly uninitialized variable" makes sense. It is possible that sky is uninitialized and thus null which is of course not compatible with the type string. Null !== string.

After a quick look around the docs I found that you can prepend a type with a '?' to introduce a "maybe type". Since sky may or may not exist this seemed like a reasonable approach. Tests pass and flow is happy. 😎

screen shot 2017-09-02 at 10 23 44 pm

Happily typing my way through through the file I made it to the final function. buildTimeUI is the function that (big surprise) actually builds the time data for the UI. It returns an object that I type checked like so:

screen shot 2017-09-02 at 10 24 26 pm

Why isn't this working? Object literal isn't compatible with object literal? What the hell... To get to the bottom of this I decided to run the cli and see if that offered a better explanation.

screen shot 2017-09-02 at 10 25 07 pm

OMG - is this really doing what I think it is? In this function I expect sky to be a string. But I generate that string with the getSky function above which Flow expects to be a maybe type. It may be a string or it may be null.

The two objects are not equal.

How to fix things? Instead of giving the function getSky a maybe type it should instead be typed as a 'string' and sky should be initialized as an empty string. like so:

screen shot 2017-09-02 at 10 24 46 pm

Suddenly flow is happy again!

screen shot 2017-09-02 at 10 24 56 pm

After reading the docs a bit further I was able to refactor the typing of the last function a bit to be more consistent with the others.

screen shot 2017-09-02 at 11 16 33 pm

Two bugs in a file I was pretty confident about...

My only complaint is the linter could do a better job communicating. But that's not flow's fault.

Two thumbs up. Would Flow again 👍👍