flowtypeing一个高阶函数,用于包装任意arity的函数(并改变参数和返回类型)



我有一个函数nilGuard,它包装了另一个函数(我们称之为f)并返回一个函数,如果它的任何参数undefinednull将返回默认值(默认情况下null),否则将返回将其参数应用于f的结果:

function isNil(value) {
return value === undefined || value === null
}
export function nilGuard(f, defaultValue = null) {
return (...args) => (args.some(isNil) ? defaultValue : f.apply(this, args))
}

我尝试添加类型:

function isNil(value): boolean %checks {
return value === null || value === undefined
}
// Type to represent the funtion being wrapped (and the one being returned)
// A is the type tuple(?)
// R is the return type
type F<A, R> = (...args: A) => R
// Type function that transforms a given type T into a nillable (null or undefined) version
type Nillable = <T>() => ?T | null
// Higher order type that turns a tuple into a tuple of nillables of the original types.
type Nillables<A> = $TupleMap<A, Nillable>
// A is the type of the args tuple
// R is the return type of f
// D is the type of the default value
function nilGuard<A, R, D>(f: F<A, R>, defaultValue: D = null): F<Nillables<A>, R | D> {
return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
}

不幸的是,流给出了以下错误:

19: function nilGuard<A, R, D>(f: F<A, R>, defaultValue: D = null): F<Nillables<A>, R | D> {
^ null. This type is incompatible with
19: function nilGuard<A, R, D>(f: F<A, R>, defaultValue: D = null): F<Nillables<A>, R | D> {
^ D
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
^ call of method `some`. Method cannot be called on
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
^ A
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
        ^ A. This type is incompatible with
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
        ^ $Iterable
Property `@@iterator` is incompatible:
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
        ^ property `@@iterator` of `$Iterable`. Property not found in
20:   return (...args: A) => (args.some(isNil) ? defaultValue : f.apply(this, args))
        ^ A

据我了解,这归结为两个问题:

  1. 参数元组A被视为不可迭代元组。这要么是调用some()的结果,要么是稍后的传播运算符 - 我相信两者都应该在元组上是安全的。
  2. 键入D,默认值。理想情况下,type D = null是默认情况,但是如果我使用第二个参数调用nilGuardD应该获得第二个参数的类型。Flow 似乎假设我的类型参数 D 本身不是可为空的类型。

我在尝试的底部包含两个函数定义,我之前链接到这两个定义,以说明我希望此函数实现的函数类型转换类型:

function prefix(path: string): string {
return `https://example.com${path}`
}
function add(x: number, y: number): number {
return x + y
}
const guardedPrefix: (?string | null) => (string | null) = nilGuard(prefix)
const guardedAddWithDefault: (?number | null, ?number | null) => number = nilGuard(add, 0)

感谢所有帮助/建议:)

您已确定流错误的源。我想详细说明为什么这些是问题。

参数元组 A 被视为不可迭代元组。这要么是在其上调用 some() 的结果,要么是稍后的传播运算符 - 我相信两者都应该在元组上是安全的。

您已经指定args元组的类型为A,并且您没有对变量A施加任何约束。Flow 认为这意味着可以A分配任何类型,包括不可迭代的类型。您是对的,args是可迭代的。您所要做的就是为A指定一个与 rest 参数列表兼容的约束。那可能是A: Array<mixed>.但您实际上不必自己选择约束 - 您可以简单地指示存在约束,并让 Flow 使用*注释推断约束应该是什么:

function nilGuard<A: *, R, D>

键入 D,默认值。理想情况下,类型 D = null 将是默认情况,但是如果我使用第二个参数调用 nilGuard,D 应该获得第二个参数的类型。Flow 似乎假设我的类型参数 D 本身不是可为空的类型。

这本质上是相同的问题:您没有对D施加任何约束,这告诉 FlowD可以采用任何类型,包括不可为空的类型。您可以通过像这样更改defaultValue类型来解决此问题:

defaultValue: ?D = null

这告诉 Flow ,无论为D选择哪种类型,defaultValue都有额外的可能性被null。但我会以不同的方式处理这个问题。(更多内容见下文。

还有几个问题:

Nillable<T>的定义必须用类型T注释参数位置。该定义应如下所示:

type Nillable = <T>(_: T) => ?T

如果未在参数位置给出T则 Flow 无法将返回类型与输入类型相关联。

Nillable<T>的定义中,您使用多余的类型?T | null?TT | void | null的简写,其中voidundefined的类型。

所以这就是我会怎么做。如果调用方指定defaultValue,则希望nilGuard默认情况下返回该值。但是,如果调用方不提供defaultValue则默认返回值应为null(或undefined,这将更惯用)。实现此目的的最干净方法是对nilGuard类型使用重载签名。您希望nilGuard的行为有所不同,具体取决于提供的参数数量。所以你有一个签名,看起来像这样:

function nilGuard<A: *, R, D>(f: F<A, R>, defaultValue: D): F<Nillables<A>, R | D>

还有一个独特的签名,如下所示:

function nilGuard<A: *, R>(f: F<A, R>): F<Nillables<A>, ?R>

(顺便说一句,我认为您使用$TupleMap来定义Nillables非常漂亮!

在运行时,这些是相同的函数。但 Flow 可以跟踪重载签名,并根据给出的参数为呼叫站点选择适当的签名。不幸的是,声明重载函数有点尴尬,因为Javascript没有本机重载语法。最简单的选择是使用 Flowdeclare语句声明所有函数签名,并单独编写实际定义:

declare function nilGuard<A: *, R, D>(f: F<A, R>, defaultValue: D): F<Nillables<A>, R | D>
declare function nilGuard<A: *, R>   (f: F<A, R>): F<Nillables<A>, ?R>
function nilGuard(f, defaultValue) {
return (...args) => args.some(isNil) ? defaultValue : f.apply(this, args)
}

重载签名允许 Flow 跟踪返回值是否可能undefined,或者类型是否是底层函数返回类型和defaultValue类型的精确并集。例如,如果DR的类型相同,那么您可以节省一些undefined检查。

使用declare的一个不幸效果是 Flow 不会检查nilGuard本身的定义是否正确。但这将为您提供调用nilGuard时所需的类型检查行为。重载函数的另一种形式在内部检查函数定义,如下所示:

type NilGuard =
& (<A: *, R, D>(f: F<A, R>, defaultValue: D) => F<Nillables<A>, R | D>)
& (<A: *, R>   (f: F<A, R>) => F<Nillables<A>, ?R>)
const nilGuard: NilGuard = (f, defaultValue) =>
(...args) => args.some(isNil) ? (defaultValue: any) : f.apply(null, args)

这是因为 Flow 将重载函数视为多个函数类型的交集。但是nilGuard的行为非常棘手,以至于我无法让这个表单进行类型检查。

最新更新