我有一个函数nilGuard
,它包装了另一个函数(我们称之为f
)并返回一个函数,如果它的任何参数undefined
或null
将返回默认值(默认情况下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
据我了解,这归结为两个问题:
- 参数元组
A
被视为不可迭代元组。这要么是调用some()
的结果,要么是稍后的传播运算符 - 我相信两者都应该在元组上是安全的。 - 键入
D
,默认值。理想情况下,type D = null
是默认情况,但是如果我使用第二个参数调用nilGuard
,D
应该获得第二个参数的类型。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
:?T
是T | void | null
的简写,其中void
是undefined
的类型。
所以这就是我会怎么做。如果调用方指定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
类型的精确并集。例如,如果D
与R
的类型相同,那么您可以节省一些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
的行为非常棘手,以至于我无法让这个表单进行类型检查。