首页 > 解决方案 > 是否可以流式输入一个返回另一个可能被柯里化的函数的函数?

问题描述

假设您有一个返回另一个函数的函数,并且返回的该函数可能会或可能不会被柯里化。这可以是流式的吗?我猜答案可能是否定的。

这是一个例子:

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)

对于此代码,flow 给出以下错误:

add(2, 3)
           ^ Cannot call `add` because no more than 1 argument is expected by function [1].
References:
3:     return function add(x) {
              ^ [1]

本质上,flow 无法确定返回了哪种函数样式。也不能输入接收函数的变量,例如:

const add: (number, number) => number = getAddFn(false);

在这种情况下,流程抱怨:

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]

谁能解释这是否可能?或者如果不是,清楚地解释为什么?我可以看到问题可能与传入的布尔值的值getAddFn直到运行时才知道这一事实有关。

标签: javascripttypesflowtype

解决方案


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:

  • by passing it to something which can accept both an A and a B
  • by passing it to something which can separate out an A from a B, such as an if statement

The 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.


推荐阅读