Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flowtype constantly requiring null checks

Tags:

flowtype

I'm wondering how to avoid these numerous null checks or at least understand what the point is because it seems counter-productive.

Flowtype is giving me an error for this if I omit the null check:

var myEl = new MyElement()
if (document.body != null) { // error on next line if omitted
    document.body.appendChild(myEl)
}

I have to do that null check for the document body in every single callback too, because who knows, maybe the body is null here right?! I think this is total overkill. Not only that, but what's the point of such a simple nullcheck? It will just silently skip over a vital part of the program and exhibit undefined behavior somewhere else and make debugging the app that much harder. I'd really prefer just having a null exception at this point if an error ever happens here, because to be really sure this tiny 2-line code segment that I'd write in javascript would have to be like this in flowtype:

var myEl = new MyElement()
if (document.body != null) {
    document.body.appendChild(myEl)
} else {
    console.error("null error")
}

So 4 additional code lines and some nesting just to trace something I'd get for free if I just let the app run into an error. And I need those 4 lines on every single querySelector. On every single document.body. On every single getElementByTagName. This alone probably increases my entire codebase by 10%. What's the point of enforcing this so strictly?

In other languages I'd also be able to try-catch around these hotspots gradually as needed, flow doesn't let me do that either. It shows errors whether I add a try-catch or not.

like image 329
Blub Avatar asked Feb 21 '17 20:02

Blub


2 Answers

When I know for sure that my variable won't be null and Flow doesn't, I use an unwrap() function:

export default function unwrap<T>(value: T): $NonMaybeType<T> {
  if (value !== null && value !== undefined) return value
  throw new Error('Unwrapping not possible because the variable is null or undefined!')
}
like image 97
Dominique PERETTI Avatar answered Oct 22 '22 03:10

Dominique PERETTI


By using a type checker, you are opting into the rules that it enforces. Accessing a property on a nullable type is one of those restrictions. So if you want to have exceptions for null values, you need to explicitly throw to prove to Flow that it is what you want. You could for instance make a module like

if (!document.body) throw new Error("Unexpectedly missing <body>.");
export const body: HTMLElement = document.body;

export function querySelector(el: HTMLElement, selector: string): HTMLElement {
    const result = el.querySelector(selector);
    if (!result) throw new Error(`Failed to match: ${selector}`);
    return result;
}

By throwing, these functions explicitly say "I will return an element" in all cases, and in null cases, they will throw exceptions.

Then in your normal code, you are guaranteed you can use those

import {body, querySelector} from "./utils";

body.appendChild(document.createElement('div'));

querySelector(body, 'div').setAttribute('thing', 'value');

and it will typecheck property.

like image 38
loganfsmyth Avatar answered Oct 22 '22 05:10

loganfsmyth