为什么兼容的重载签名需要“Subject & Array”?

2024-03-27

我正进入(状态:

重载签名与函数实现不兼容。ts(2394)

On:

/** Iterate through an Array. */
export default function eachr<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
): typeof subject

整个片段中:

export interface IteratorCallback<Subject, Key, Value> {
    (this: Subject, value: Value, key: Key, subject: Subject): void | boolean
}

/** Iterate through an Array. */
export default function eachr<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
): typeof subject

/** Iterate through an Object. */
export default function eachr<RecordKey extends keyof any, Value>(
    subject: Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject

/** Iterate through the subject. */
export default function eachr<RecordKey extends keyof any, Value>(
    input: Array<Value> | Record<RecordKey, Value>,
    callback: IteratorCallback<typeof input, RecordKey | number, Value>
): typeof input {
    if (Array.isArray(input)) {
        // Array
        const subject = input as Array<Value>
        for (let key = 0; key < subject.length; ++key) {
            const value = subject[key]
            if (callback.call(subject, value, key, subject) === false) {
                break
            }
        }
    } else {
        // Object
        const subject = input as Record<RecordKey, Value>
        for (const key in subject) {
            if (subject.hasOwnProperty(key)) {
                const value = subject[key]
                if (callback.call(subject, value, key, subject) === false) {
                    break
                }
            }
        }
    }

    // Return
    return input
}

我可以通过将其更改为:

/** Iterate through an Array. */
export default function eachr<Subject extends Array<Value>, Value>(
    subject: Subject & Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
): typeof subject

但是,我不明白为什么会解决这个问题。问题是什么?为什么这一改变会让问题消失?

更令我惊讶的是,如果我将相同的更改应用于纯对象迭代器函数,则会导致它失败:

/** Iterate through an Object. */
export default function eachrObject<
    Subject extends Record<RecordKey, Value>,
    RecordKey extends keyof any,
    Value
>(
    subject: Subject & Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
    for (const key in subject) {
        if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            // above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
            // below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
            if (callback.call(subject, value, key, subject) === false) {
                break
            }
        }
    }
    return subject
}

而这有效:

/** Iterate through an Object. */
export default function eachrObject<RecordKey extends keyof any, Value>(
    subject: Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
    for (const key in subject) {
        if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            if (callback.call(subject, value, key, subject) === false) {
                break
            }
        }
    }
    return subject
}

然而,这两种形式都适用于数组迭代器:

/** Iterate through an Array. */
export default function eachrArray<Subject extends Array<Value>, Value>(
    subject: Subject & Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
    for (let key = 0; key < subject.length; ++key) {
        const value = subject[key]
        if (callback.call(subject, value, key, subject) === false) {
            break
        }
    }
    return subject
}

/** Iterate through an Array. */
export default function eachrArray<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
    for (let key = 0; key < subject.length; ++key) {
        const value = subject[key]
        if (callback.call(subject, value, key, subject) === false) {
            break
        }
    }
    return subject
}

那么为什么会发生这样的变化呢Subject extends Array<Value>对于数组迭代器的重载兼容性是必要的,但是Subject extends Record<RecordKey, Value>破坏对象迭代器?

对于这里的代码量感到抱歉,这是我可以将其简化为的最小用例,其中包含所有需要考虑的因素。


老实说,这需要费力地完成很多工作,而且我认为我无法准确回答为什么你能让事情发挥作用。在我看来,您的过载签名都应该失败。让我们看一个超级简单的重载/实现示例:

function foo(x: string): void; // narrower, okay 
function foo(x: string | number | boolean): void; // wider, error
function foo(x: string | number): void {} // impl

请注意第二个重载签名如何给出与实现签名不兼容的错误。那是因为超载x is a wider类型比实现的类型x。并且重载需要narrower types.

还要注意一般情况下(因为--strictFunctionTypes在 TypeScript 2.6 中引入 https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#strict-function-types) 函数类型有逆变在它们的参数类型中。这会导致以下行为:

type StringAccepter = (x: string) => void;
const helloAccepter: StringAccepter = (x: "hello") => {}; // error
const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay

helloAccepter不是有效的StringAccepter因为"hello"string, while stringOrNumberAccepter is一个有效的StringAccepter因为string | numberstring。因此,函数参数变宽会使它们的函数变窄,反之亦然:

function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider 
function bar(cb: StringAccepter): void {} // impl

所以我预计你的两个重载都会失败,因为实现签名callback type (IteratorCallback<typeof input, RecordKey | number, Value>)实际上比您的任何一个呼叫签名都窄callback类型。

此时,不要尝试费力地解决涉及额外问题的可能解决方案Subject类型参数并理解为什么有些东西有效而有些东西不起作用(这让我的大脑受伤......也许有编译器错误?也许没有?谁知道),我会选择我建议的解决方案.. . 使实现签名真正足够宽以支持两个调用签名:

/** Iterate through an Array. */
export function eachr<Value>(
  subject: Array<Value>,
  callback: IteratorCallback<typeof subject, number, Value>
): typeof subject

/** Iterate through an Object. */
export function eachr<RecordKey extends keyof any, Value>(
  subject: Record<RecordKey, Value>,
  callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject

/** Iterate through the subject. */
export function eachr<RecordKey extends keyof any, Value>(
  input: Array<Value> | Record<RecordKey, Value>, 
  // here is the change
  callback: IteratorCallback<Array<Value>, number, Value> |
    IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
): typeof input {
  if (Array.isArray(input)) {
    // Array
    const subject = input as Array<Value>
    // a new assertion:
    const cb = callback as IteratorCallback<Array<Value>, number, Value>;
    for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (cb.call(subject, value, key, subject) === false) {
        break
      }
    }
  } else {
    // Object
    const subject = input as Record<RecordKey, Value>
    // a new assertion:
    const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
    for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
        const value = subject[key]
        if (cb.call(subject, value, key, subject) === false) {
          break
        }
      }
    }
  }

  // Return
  return input
}

区别在于callback实现签名上的参数是类似类型的真正联合callback每个调用签名上的参数。此外,实现本身需要进行缩小断言callback to cb与您已经执行的断言大致相同input to subject.

现在编译器应该很高兴。希望有帮助;祝你好运!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么兼容的重载签名需要“Subject & Array”? 的相关文章

随机推荐

  • HTTP 413 请求实体太大

    我目前无法在 drupal 7 7 15 中的自定义文件字段上使用 Drupal FileField Source 远程 url 选项 发布非常大的文件 文件已成功上传到 tmp 目录 但在尝试发布时失败 文件大小为870Mb A 510m
  • 如何在yml映射的实体中配置VichUploader?

    我有一个名为 杂志 的实体 从 yml 文件映射 Acme DemoBundle Entity Magazine type entity table magazine id id type integer generator strateg
  • 为什么参数依赖查找不适用于函数模板dynamic_pointer_cast

    考虑以下 C 程序 include
  • jQuery 获取选中复选框的标签

    在下面的代码中 当我检查 墨西哥 时 我不断收到 MexicoMexico 作为标签文本返回 对于所有其他字段 我没有得到这个重复的结果 它仅适用于这一字段 该问题在第一次分配countryvalues i 后立即发生 我不明白为什么 di
  • NHibernate SchemaUpdate 在生产代码中安全吗?

    为了简单起见 我在运行时将 Fluent NHibernate 的 Automapping 与 NHibernate 的 SchemaUpdate 结合使用 每次运行时 Automapper 都会为所有实体类创建映射 而 SchemaUpd
  • Swagger / Open API 2.0 我可以声明通用响应标头吗?

    是否可以声明一个自定义响应标头 该标头将出现在所有响应中 而无需将其复制到每个响应结构中 这在 OpenAPI 3 0 中有所改进 您现在可以在全局中定义通用标头components headers部分然后 ref这些定义而不是重复内联定义
  • HTTP 标头中 CRLF 序列的不正确中和

    我在我的项目上运行了 Veracode 扫描 它在 HTTP 响应拆分下给了我 CWE ID 113 问题 我尝试根据建议解决该问题 但没有成功 例如 try String selNhid req getParameter selNhid
  • 使用 JWT 刷新令牌如何安全?

    据我了解 您可以缩短 JWT 访问令牌的生命周期 这样如果有人可以访问它 它就不会长期工作 但是 我们不会对 JWT 刷新令牌执行相同的操作来增强用户体验 但现在 如果有人可以访问我的 JWT 刷新令牌 这将授予他们访问受保护资源的权限 那
  • Haskell 二级多态编译错误

    给出以下定义 import Control Monad ST import Data STRef fourty two do x lt newSTRef 42 Int readSTRef x 在 GHC 下编译如下 main print r
  • dplyr left_join 按小于、大于条件

    这个问题与问题有些相关根据不平凡的标准有效合并两个数据帧 https stackoverflow com questions 18840410 efficiently merging two data frames on a non tri
  • pandas 在构造特定数据类型的数据框时是否有默认填充值?

    考虑字典d d A x 1 y 1 B y 1 z 1 当我把这个传递给pandas DataFrame http pandas pydata org pandas docs stable generated pandas DataFram
  • Hadoop MapReduce 提供嵌套目录作为作业输入

    我正在从事一项处理嵌套目录结构的工作 其中包含多个级别的文件 one three four baz txt bleh txt foo txt two bar txt gaa txt 当我添加one 作为输入路径 不会处理任何文件 因为没有文
  • AppleScript:从应用程序隐藏/获取进程名称

    我想隐藏最前面的应用程序 我知道您可以使用以下语法隐藏进程 tell application System Events set visible of process to false end tell 我知道如何获取最前面的应用程序 pa
  • 如何从字符串列表文件中 grep 精确匹配

    我有一个文件 A 其中有一列 其中包含如下字符串列表 ADAMTS9 AIP 我想使用文件 A 中的字符串来 grep 文件 B 中包含它们的行 文件 B 如下所示 chr13 50571142 50592603 ADAMTS9 21461
  • 如何使用 Excel JavaScript API Office 加载项最大限度地提高表行添加 50K+ 行的性能

    我正在尝试向表中添加大量行 我的项目需要添加大表 请告诉我是否有更好的替代方案来最大限度地提高性能 我应该使用 Range 对象 API 吗 代码如下所示 function createSampleSheet numberOfTimes s
  • 获取 R 文件而不运行它(单元测试)

    我们正在构建一个 R 代码库 并希望对我们编写的任何函数进行单元测试 到目前为止 我们已经找到了两个 R 测试库 RUnit 和 testthat 在进行了一些沙箱处理之后 我们开发了一种可靠的方法来在每次运行时测试代码 例如 sample
  • getch() 和 _getch() 之间的区别

    conio h 头文件中定义的两个函数有什么区别 getch 和 getch 声明有区别吗 或者仅仅是由于更新的标准造成的差异 这是微软几年前决定更严格地解释 C 标准的一部分 它表示全局名称空间中以下划线开头的所有名称都是保留供实施使用
  • 带有垂直 ytick 标签的条形图

    我正在使用 matplotlib 生成 垂直 条形图 问题是我的标签相当长 有什么方法可以垂直显示它们 无论是在栏中还是在其上方或下方 你的意思是这样的吗 gt gt gt from matplotlib import gt gt gt p
  • OSX Lion 上的 GDB 7.3.1

    我正在尝试使用 macports 中的 GDB v 7 3 1 来调试用以下命令编译的可执行文件 g 4 7 也来自 macports 但是 我启动调试器 得到以下输出 GNU gdb GDB 7 3 1 版权所有 C 2011 自由软件基
  • 为什么兼容的重载签名需要“Subject & Array”?

    我正进入 状态 重载签名与函数实现不兼容 ts 2394 On Iterate through an Array export default function eachr