我有一个函数 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))
}
我试过了 adding types :
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
据我了解,这归结为两个问题:
- args 元组
A
,被视为不可迭代。这要么是对其调用some()
的结果,要么是后来的传播运算符的结果 - 我相信两者在元组上应该是安全的。 D
的类型,默认值。理想情况下,type D = null
将是默认情况,但如果我用第二个参数调用nilGuard
,D
应该获得该类型第二个参数。 Flow 似乎假定我的类型参数 D 本身不是可空类型。
我在 the attempt that I linked to earlier 的底部包含了两个函数定义。为了说明我希望此函数实现的函数类型转换的种类:
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)
感谢所有帮助/建议:)
最佳答案
您已确定流程错误的来源。我想详细说明为什么会出现这些问题。
The args tuple, A, being treated as non-iterable. This is either a result of calling some() on it, or the spread operator later on - both should be safe on tuples, I believe.
您已指定 args
的类型元组是 A
,并且您没有对变量 A
施加任何约束. Flow 认为这意味着 A
可以分配任何类型,包括不可迭代类型。你是对的 args
是可迭代的。您所要做的就是为 A
指定一个约束条件。与其余参数列表兼容。那可能是 A: Array<mixed>
.但是您实际上不必自己选择约束 - 您可以简单地指出存在约束并让 Flow 使用 *
推断约束应该是什么注释:
function nilGuard<A: *, R, D>
The typing of D, the default value. Ideally, type D = null would be the default case, but if I invoke nilGuard with a second argument, D should gain the type of that second argument. Flow seems to assume that my type parameter D is itself not a nullable type.
这本质上是同一个问题:您没有对 D
施加任何约束。 ,它告诉 Flow D
可以采用任何类型,包括不可为空的类型。您可以通过更改 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 没有原生的重载语法。最简单的选择是使用 Flow declare
声明所有函数签名的语句,并分别编写实际定义:
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
的行为非常棘手,我无法对这个表单进行类型检查。
关于javascript - 流式化高阶函数以包装任意数量的函数(并改变参数和返回类型),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48512016/