TypeScript 类型缩小似乎做出了错误的假设

2024-01-16

即使第 4 行访问了以下代码中的属性,也没有 TypeScript 错误x那不存在。

type T = {num1: number, num2: number} | {str1: string, str2: string}
let x: T = {num1: 1, num2: 2, str1: 'hello'};
if('str1' in x) {
  console.log(x.str2.toUpperCase());
}

这是 TypeScript 的错误吗?或者有人可以向我指出解释此行为的文档吗?似乎 TypeScript 不应该允许在第 2 行进行赋值,或者不应该假设'str1' in x暗示'str2' in x.


来自纯粹健全性 https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness从角度来看,作业是正确的并且in算子缩小 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing是不正确的。从务实的角度来看,in运算符缩小非常有用,而赋值则不然。这两个功能不匹配,因此您提供了一个很好的 TypeScript 代码示例,对于该示例,类型纯粹主义者和惯用的 JS 编码人员都不会对该语言的行为特别满意。


首先我们看一下任务

let x: T = {num1: 1, num2: 2, str1: 'hello'};

根据结构类型 https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html#structural-type-system,初始化器x绝对是一个类型的值T,也是如此{num1: 1, num2: 2, str1: true, randomThingy: "x"}。额外的属性不会违反类型。

但 TypeScript 确实执行超额财产检查 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks作为一种 linter 规则来捕获人们丢弃类型信息的情况。当你分配时const x: {a: string} = {a: "", b: 0},你正在丢弃有关的信息b属性,因为编译器无法恢复它;编译器会忘记b完全。另一方面,const y = {a: "", b: 0}; const x: {a: string} = y;很好,因为即使x不知道b, y仍然如此。多余的属性检查规则是对实用主义的认可,而不是类型安全。

这条规则有时会让人们认为 TypeScript 中的对象类型不允许额外的属性,并且它们是 中所要求的“精确类型”微软/TypeScript#12936 https://github.com/microsoft/TypeScript/issues/12936。但 TypeScript 没有精确的类型,并且多余的属性检查规则并没有在人们认为应该执行的任何地方执行。

其中一个地方是当您分配给类似的类型时T, a 联合型 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types这不是一个受歧视联盟 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions。在执行多余的属性检查之前,编译器不会将联合拆分为其成员。只要联合体中至少有一个成员需要某个属性,编译器就会对此感到满意。有一个长期悬而未决的问题微软/TypeScript#20863 https://github.com/microsoft/TypeScript/issues/20863要求改变这一点,但在可预见的未来,情况就是这样。

因此,如果您主要关心类型安全,则分配很好,但如果您关心确切类型,则分配不好。


现在我们来看看缩小范围

if('str1' in x) {
  console.log(x.str2.toUpperCase());
}

这显然不是健全的行为。毕竟,TypeScript 中的对象类型并不精确,所以如果您知道"str1"是一个关键x你不能从逻辑上得出结论"str2"也是一把钥匙。如果你非常关心类型安全,这是非常不幸的。

另一方面,它非常有用。即使你在技术上不能这么说"str1"绝对意味着存在"str2",它们之间的相关性可能非常强。一般来说,当人们检查仅已知存在于工会一名成员的财产时,他们这样做是因为这确实让他们在实践中(如果不是理论上)歧视工会。

In 微软/TypeScript#15256 https://github.com/microsoft/TypeScript/pull/15256,实施的拉取请求in算子缩小,有TS 团队的开发负责人发表了以下评论 https://github.com/microsoft/TypeScript/pull/15256#discussion_r154843152:

我们考虑了健全性方面,它与围绕具有未声明属性的别名对象的现有健全性问题没有太大不同。 ... 现实情况是,大多数联合已经正确脱节,并且别名不足以体现问题。有人写一个in测试不会写出“更好”的检查;实践中真正发生的只是人们添加类型断言或将代码移动到同样不健全的用户定义类型谓词。在网上,我认为这并不比现状更糟糕(而且更好,因为它减少了用户定义的类型谓词,如果由于缺乏拼写检查而使用 in 编写,则很容易出错)。

So in操作员缩小范围绝对是实用主义战胜稳健性的一个例子。


就这样……务实地说,没有人会为它分配一个奇怪的跨联盟值x,如果编译器能够在您这样做时发出警告,那就太好了。从技术上讲,使用它是错误的k in o暗示除某个键之外的任何内容k,编译器不应该这样做。

但退一步来说,这只是really对于纯粹主义者来说是一个问题。 TypeScript 的类型系统并不完全健全,也无意如此(并且尝试使用编译为 JavaScript 的东西来做到这一点是一个“傻瓜差事” https://github.com/microsoft/TypeScript/issues/9825#issuecomment-306272034根据同一开发负责人的说法)。这TypeScript 的第一个设计目标 https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals是为了捕捉可能的错误,显然很少有人像这个例子那样不小心搬起石头砸自己的脚。

语言的一部分偏向于健全性,而另一部分则偏向于实用主义,这一事实可以被视为语言设计的实用主义:健全性在帮助人们编写无错误程序方面是伟大的,但如果它会被其他东西所取代,那么它就会被抛弃。它带来的伤害大于帮助。再次引用 https://github.com/microsoft/TypeScript/issues/9825#issuecomment-350921122:

最终,类型系统是我们创建的工具,用于帮助我们编写正确的程序。可证明的稳健性是代理措施为了这个努力。如果您的程序在构造上仅在运行时观察到正确的类型,那么计算机是否可以证明这种情况是该短语的两种含义的学术练习。

这意味着,如果您希望看到此示例发生一些变化,您就必须证明此类错误比 TS 团队想象的更常见。通常使用来自运行其中的流行包的真实代码示例。

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

TypeScript 类型缩小似乎做出了错误的假设 的相关文章

随机推荐

  • 递归函数的返回值是“未定义”

    每当我执行此代码片段时 返回前的 console log 都会返回值 23 的 20 倍的数组 但是 console log Check users 0 20 仅返回 未定义 我究竟做错了什么 var users 23 23 23 23 2
  • 如何在 cucumber-jvm 步骤之间传递变量

    为了在步骤之间传递变量 我让步骤方法属于同一类 并使用该类的字段来传递信息 举例如下 Feature Demo Scenario Create user Given User creation form management When Cr
  • 将文件中的html表格输入R Markdown,编织到Word?

    我正在处理一个 R Markdown 文件 我们需要能够将其编织为 pdf 和 Word 对于合著者 我们还生成了回归表stargazer由于数据大小 需要单独计算并创建两个文件 regression table tex 和regressi
  • 具有非堆叠百分比系列的 Highcharts 条形图

    我想创建具有非堆叠百分比值的 Highcharts 条形图 I use plotOptions bar stacking percent 但是这个将所有系列叠加到另一个系列之上才能达到 100 我希望每个系列都呈现为不同的栏 除了自己处理系
  • 闹钟应用源代码

    有没有办法获得Alarm Application用于自定义现有默认源代码的源代码Android 2 3 您可以从 Android github 镜像下载该应用程序 https github com android platform pack
  • PHP XML Expat 解析器:如何只读取 XML 文档的一部分?

    我有一个具有以下结构的 XML 文档
  • swing - 触发树单元编辑事件

    我有一个带有可编辑节点的 JTree 如何以编程方式触发树单元格编辑事件 即调出节点重命名文本框来代替突出显示的节点 就像用户手动突出显示它并按 F2 一样 基本上 我想添加一个 重命名 菜单项或工具栏按钮 以提示用户了解树的特定功能 并且
  • 如何使用媒体查询重新排序 HTML? [复制]

    这个问题在这里已经有答案了 我想换一个div通过媒体查询确定元素的位置 按查看文档的顺序 div 1 div div 2 div 当我改变视口时我想要div 1留在div 2 基本上div 1在顶端 但我想通过媒体查询更改它 可以吗 Use
  • 如何使用 boto3 等待 dynamodb 更新表或 ACTIVE 状态

    我正在更改表格 例如使用 boto3 的容量设置 然后我需要等待它完成 我更喜欢使用的解决方案boto3 resource dynamodb Table MyTable 而不是 dynamodb 客户端 尝试这个让你的程序等到表更新已完成
  • 无法在 servlet 中获取 <...> 中包含的文本区域内容

    我在 HTML 页面中有一个带有文本区域的表单 我试图通过 POST 将其内容发送到 servlet 问题是如果我写类似的东西unenclosed
  • Tomcat 6 Eclipse 配置——锁定服务器位置以进行编辑

    我正在尝试更改运行 Eclipse EE 1 4 1 的 Tomcat 服务器的默认位置 以便它使用原始 Tomcat 安装 在控制台之外独立运行时工作正常 而不是 Eclipse 在工作区中创建的安装按照这个线程 https stacko
  • Perl 系统调用必须准确发送单引号和双引号 ' " 这两个字符

    Perl 系统调用必须将以下字符串发送到UnixShell XYZ 在我的 Perl 脚本中 我使用了以下命令 system cleartool mkattr replace ATTRIBUTE attribute lbtype label
  • NestedScrollView 内的子级未覆盖屏幕的整个高度

    我在我的片段中使用 NestedScrollView 在我的 xml 中 RelativeLayout 内 但它没有覆盖屏幕的整个高度 下面是我的代码
  • 重新初始化 Tensorflow 中的变量

    我正在使用张量流tf Saver要加载预训练的模型 我想通过擦除 重新初始化为随机 适当的权重和偏差来重新训练其一些层 然后训练这些层并保存训练后的模型 我找不到重新初始化变量的方法 我试过tf initialize variables f
  • 用于转义 LIKE 运算符通配符搜索的 T-SQL 特殊字符

    SQL Server 有 LIKE 运算符来处理通配符搜索 我的客户希望在应用程序的用户界面中使用 星号 字符作为通配符 我只是想知道在执行 LIKE 通配符搜索之前 除了 百分比 字符本身之外 是否还有需要担心的标准字符 在 SQL Se
  • 如何从 Android 调用导航 Web 服务

    我想调用在 Microsoft Dynamics nav ERP 中运行的 Web 服务我正在使用 ksoap2 库 但问题是每次我运行我的应用程序时它都会抛出 java net ConnectException localhost 127
  • Qt sqlite部署exe

    我有一个Qt exe从 Visual Studio 2005 构建 在获取 cpp h moc ui 文件 我做了一些简单的 QSqlite 查询 它在我的开发电脑上运行良好 但在另一台电脑上 它因以下行而崩溃 QSqlDatabase m
  • 奇怪的C++代码片段

    我有这个片段 template
  • Excel - 将多列合并为一列

    我有多个列表 它们位于 Excel 的不同列中 我需要做的就是将这些数据列合并成一个大列 我不在乎是否有重复的条目 但我希望它跳过每列的第 1 行 另外 如果 ROW1 有从 1 月到 12 月的标题 并且列的长度不同并且需要合并为一个大列
  • TypeScript 类型缩小似乎做出了错误的假设

    即使第 4 行访问了以下代码中的属性 也没有 TypeScript 错误x那不存在 type T num1 number num2 number str1 string str2 string let x T num1 1 num2 2 s