Reputation: 5510
Let's say you have a function that returns another function, and that function that is returned may or may not be curried. Can this be flow typed? I'm guessing that the answer may be no.
Here's an example:
function getAddFn(curried) {
if (curried === true) {
return function add(x) {
return function(y) {
return x + y;
}
}
}
return function add(x, y) { return x + y };
}
const add = getAddFn(false);
add(2, 3)
For this code, flow gives the following error:
add(2, 3)
^ Cannot call `add` because no more than 1 argument is expected by function [1].
References:
3: return function add(x) {
^ [1]
Essentially, flow is unable to determine which function style has been returned. It's also not possible to type the variable that receives the function, e.g.:
const add: (number, number) => number = getAddFn(false);
In this case, flow complains:
13: const add: (number, number) => number = getAddFn(false);
^ Cannot assign `getAddFn(...)` to `add` because function [1] is incompatible with number [2] in the return value.
References:
4: return function(y) {
^ [1]
13: const add: (number, number) => number = getAddFn(false);
^ [2]
Can anyone explain if this is possible? Or if not, clearly explain why? I can see that the problem could be related to the fact that the value of the boolean passed into getAddFn
is not known until runtime.
Upvotes: 4
Views: 254
Reputation: 348
While it's true that Flow doesn't know whether curried
is true or false, you're hitting a much more fundamental limitation than that. In flow, a union type x : A | B
doesn't mean that x is either A or B, and that Flow doesn't have enough information to distinguish them. It means that x has the type A | B
. The type A | B
can only be consumed in two ways:
if
statementThe distinction is subtle, but it gets clearer if you think about what the type checking algorithm is doing. First, Flow checks that the type annotation of your getAddFn
is correct. The function returns number => number => number
in one place, and (number, number) => number)
in another. Flow thus infers the the type (number => number => number) | ((number, number) => number)
for the function.
Then, Flow holds onto this type and completely forgets about the function body. This is what I mean by the distinction above; once Flow knows that a function returns a union type, it knows everything it wants to about that function, and won't revisit it. Given this and the first bullet point above, the errors hopefully start to make sense.
As an aside, hopefully one day this sort of thing will be possible if Flow ever gets proper support for intersection-typed functions. Intersection types are documented here, but there are open github issues on the fact that they don't work with functions, such as this one here. If they worked properly, then it might be possible to use them alongside Flow's literal types to implement something like this. The trick would be to overload the function's type to say that it is both a function from the literal value true
to something of type number => number => number
and a function from the literal value false
to something of type (number, number) => number
. Alas, we currently don't have the ability to do this, and it is unclear whether we ever will.
Upvotes: 1
Reputation: 78262
It seems flow doesn't like that your signature appears to be
num -> num | num -> num -> num
This means the exact type would have to be checked at runtime.
Upvotes: 0