javascript - 流式化高阶函数以包装任意数量的函数(并改变参数和返回类型)

标签 javascript flowtype

我有一个函数 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))
}

我试过了 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

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

  1. args 元组 A,被视为不可迭代。这要么是对其调用 some() 的结果,要么是后来的传播运算符的结果 - 我相信两者在元组上应该是安全的。
  2. D的类型,默认值。理想情况下,type D = null 将是默认情况,但如果我用第二个参数调用 nilGuardD 应该获得该类型第二个参数。 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这是多余的:?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 没有原生的重载语法。最简单的选择是使用 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 类型的精确并集.如果例如 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 的行为非常棘手,我无法对这个表单进行类型检查。

关于javascript - 流式化高阶函数以包装任意数量的函数(并改变参数和返回类型),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48512016/

相关文章:

带有延迟的 JavaScript/jQuery 鼠标悬停事件

javascript - JSDoc 必需参数和默认值

javascript - 在转译流程/ typescript 后保留类型检查

javascript - 单击 MS CRM 2011 后禁用功能区按钮?

javascript - Google Maps Javascript API v3 - 如何显示公司的完整信息框

flowtype 如何用可选字段注释联合

javascript - 使用 iframe 时输入检查 instanceof 的安全方法?

javascript - $FlowExpectedError 有什么作用?

javascript - 流字符串与字符串文字不兼容?

javascript - ReactJS - 有没有办法通过在 &lt;input/> 中按下 'Enter' 键来触发方法?