Flow: Flow handles default parameters incorrectly (may actually be: "?" should not mean both null and undefined)

Created on 6 Mar 2018  路  3Comments  路  Source: facebook/flow

Two functions that are exactly the same. Both work, but Flow says that one does not.

Flow Try link

const fnWithError = (n: ?number = 0): boolean => n > 42;
const fnThatWorks = (n?: number = 0): boolean => n > 42;

Result:

1: const fnWithError = (n: ?number = 0): boolean => n > 42;
                                                    ^ Cannot compare null or undefined [1] to number [2].
References:
1: const fnWithError = (n: ?number = 0): boolean => n > 42;
                           ^ [1]
1: const fnWithError = (n: ?number = 0): boolean => n > 42;
                                                        ^ [2]

I know one (to Flow) means "total absence of a value" vs. "a value was given but it was undefined".

However, this is not about the parameters, this is about the value that will be found at the location of use of the parameter! That value clearly is NOT "null or undefined" because the default parameter will be assigned.

question

Most helpful comment

Parameters are not initialized to their defaults if null is passed:

fnWithError(null) // n is equal to null, not 0

All 3 comments

Parameters are not initialized to their defaults if null is passed:

fnWithError(null) // n is equal to null, not 0

But NO VALUE does. You have to go out of your way to deliberately send a null. To create null you have to DO something first, you don't get a null from nothing.

The source of those values is function parameters that may be missing or undefined. It can get quite confusing when one tries to have such values and you are several levels removed — the examples only look easy when you have a simpel example with no call levels. If the first function is function(param1: ?string), for example, or it is function(param1?:string), you end up with different possibilities. But context sometimes requires one or the other, even though you really just want to say THIS VALUE MAY BE UNDEFINED.

For example, if you DO provide a value from a function parameter to another function that you call the value is there, but it is undefined. To Flow that is different than a value that is absent entirely, even though it's also just undefined.

Mixing all of that up with null, an entirely different issue and beast, is bad, just the various ways to interpret undefined already are quite messy (option 1: you call a function and don't provide an optional parameter at all, option 2: you provide a parameter but since its an optional parameter from the parent function that you are in while there is a variable in that parameters slot it really can be undefined as well, but to Flow it's different).

Example

Note that this is simplified, only demonstrating the problem.

function fn1 (param1?: string) {
    // ...
}

const myFn = (): ?string => '';

const result = myFn();

fn1(result);

Result:

11: fn1(result);
        ^ Cannot call `fn1` with `result` bound to `param1` because null or undefined [1] is incompatible with string [2].
References:
7: const myFn = (): ?string => '';
                    ^ [1]
3: function fn1 (param1?: string) {
                          ^ [2]

The Problem

The problem is that the "?" is interpreted in two different ways: When it's with the type it means null or undefined. When it is with a function parameter it just means undefined.

That causes problems when I have a function with an optional parameter and I want to feed into that function a variable that can be undefined, and I use ?MyType to specify that the value may be missing. Because now I ALWAYS get the additional null.

The mixing of null and "no value" (undefined) is BAD! Two entirely different things. And I have no way, unless I do it manually. But I can't even use undefined as type, in my above example I would have to write "void | MyType":

const myFn = (): void | string => '';
// instead of
// const myFn = (): ?string => '';

Here is how such a problem looks like in my code:

image

Note how the "?"-ified return value of one function cannot be used for an optional parameter without null check. Or I declare the optional parameter differently by putting the "?" in front of the type, but that means I declare it null-able even though it merely is undefined or there and there is no null possibility.

My workaround

Two new types to wrap types in undefined and null:

/**
 * This is a shortcut type so that instead of having to write the lengthy `void | T` one can
 * simply write `V<T>`. This is better than writing `?T` because that would include `null`.
 * @typedef {undefined|*} V
 */
export type V<T> = void | T;

/**
 * This is a shortcut type so that instead of having to write the lengthy `null | T` one can
 * simply write `N<T>`. This is better than writing `?T` because that would include `undefined`.
 * @typedef {null|*} N
 */
export type N<T> = null | T;

I still wish the Maybe type would not mix two entirely different things though.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ctrlplusb picture ctrlplusb  路  3Comments

Beingbook picture Beingbook  路  3Comments

mmollaverdi picture mmollaverdi  路  3Comments

mjj2000 picture mjj2000  路  3Comments

john-gold picture john-gold  路  3Comments