为什么 TypeScript 不简化类型与其超类型之一的交集?

2024-04-10

有没有办法让 TypeScript 的检查器简化交集类型中不必要的元素,或者我错认为它们是不必要的?

IIUC,类型SubType & SuperType相当于SubType,但 typescript 似乎没有执行这种简化。

如下所示,我定义了一个class Sub作为两者的声明子类型class Base and interface I.

然后,我为每个需要的超类型定义通用函数<T>如果它通过了该超类型的守卫,则返回T & 超级类型.

我希望如此T & 超级类型将简化为T因为每一个T is a 超级类型但类型检查器不会进行这种简化。

interface I {
    readonly isI: true
}

class Base {
    // HACK: Having a private member forces nominal typing
    private readonly isBase: true = true;
    toString() {
        // Use the private field to quiet warning.
        return `isBase=${this.isBase}`;
    }
}

class Sub extends Base implements I {
    readonly isI: true = true
}

// Custom type guards.
function isI(x: unknown): x is I {
    return typeof x === 'object' && x !== null && 'isI' in x &&
        (x as ({['isI']: unknown}))['isI'] === true;
}

function isBase(x: unknown): x is Base {
    return x instanceof Base;
}

// Intersect with an inferred type parameters.
function andI<T>(x: T): T & I {
    if (isI(x)) {
        return x;
    } else {
        throw new Error();
    }
}

function andBase<T>(x: T): T & Base {
    if (isBase(x)) {
        return x;
    } else {
        throw new Error();
    }
}

let sub = new Sub();
let subAndI = andI(sub); // Has type (Sub & I)
let subAndBase = andBase(sub); // Has type (Sub & Base)

// Sub and (Sub&I) are mutually assignable
let sub2: Sub = subAndI;
subAndI = sub2;

TS Playground 编译此代码时不会出现错误或警告。 那let sub2: Sub = subAndI似乎通过向我表明 但类型推断(在“.D.TS”选项卡中)包括:

declare let sub: Sub;
declare let subAndI: Sub & I;
declare let subAndBase: Sub & Base;

不,如我所料

declare let sub: Sub;
declare let subAndI: Sub;
declare let subAndBase: Sub;

Sub&I似乎可以相互分配Sub但在 TS 中将两者视为同等类型是否存在风险?


我能找到的与该主题最接近的记录讨论是微软/TypeScript#16386 https://github.com/microsoft/TypeScript/issues/16386,以及我在 2017 年提交的问题,要求 TypeScript 实现所谓的吸收定律 https://en.wikipedia.org/wiki/Algebra_of_sets#Some_additional_laws_for_unions_and_intersections由此如果T extends U, then T | U将持续减少到U and T & U将持续减少到T.

我的问题最终被关闭为同时“固定”和“拒绝” https://github.com/microsoft/TypeScript/issues/16386#issuecomment-380567326。其中一些(但不是全部)行为最终得到了实施。在该问题中从未明确说明为什么没有完全实施交叉点减少,因此以下是我的(希望是受过教育的)猜测。


TL;DR:这种全面的简化要么会破坏人们所依赖的东西,要么就 TS 开发团队的时间或编译器性能而言不值得。

如果 TypeScript 的类型系统完全健全,并且类型简化是设计 TypeScript 的主要考虑因素,那么这些法则可能应该得到实施。但是,尽管类型系统的健全性是 TS 开发的指导原则之一,但它并不是唯一的原则。

TypeScript 的类型系统并不完全健全,而且也并非如此(请参阅TypeScript 设计非目标 #3 https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals,以及中的讨论微软/TypeScript#9825 https://github.com/microsoft/TypeScript/issues/9825)。因此,一些关于类型的“明显正确”的法则无法在不破坏其他东西的情况下实现。

例如,在声音类型系统中,子类型是传递的,因此T extends U and U extends V就意味着T extends V。但在 TypeScript 中并不总是如此。可选属性的行为方式不健全但有用,所以{a?: string} extends {} and {} extends {a?: number}, but not {a?: string} extends {a?: number}。在声音类型系统中,交集是可交换的,因此T & U应该是相同的类型U & T。唉,这在 TypeScript 中又并不总是如此。重载函数 https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads具有多个调用签名是依赖于顺序的,因此{(): string} & {(): number}是一个不同的类型{(): number} & {(): string}。对此所做的任何更改都必须仔细考虑,以免破坏大量现实世界的代码。

为了简化而简化也不是 TypeScript 的目标。例如,超额财产检查 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks当人们初始化具有编译器将完全忘记的属性的对象时,会发出警告,因为这通常表明存在编程错误。所以const x: {a: string} = {a: "", b: 0}产生警告,因为b财产将不会被追踪。可能是一个错误。另一方面,const x: {a: string} | {a: string, b: number} = {a: "", b: 0}不会产生警告,因为编译器会期望b财产可能存在。但{a: string} | {a: string, b: number}将减少为{a: string}如果吸收定律在所有地方都得到严格应用。这种简化并不被认为比过多的属性检查更重要。

如果自动应用类型简化规则,您将在类型检查器行为中看到可观察到的重大变化。但即使它没有造成任何破坏,它仍然可能无法实施。据推测,编译器必须花费处理时间来应用建议的缩减规则。也许这些时间可以通过下游性能节省得到回报,但这显然不是真的。有人需要花费时间和精力来实施和测试它。并且添加更多规则会使编译器行为更加复杂。即使是非破坏性的改变也需要付出很大的努力才能被认为是值得的。


无论如何,如果您真的关心简化交叉点,您可以考虑通过辅助函数手动执行此操作。无论您当前在哪里写作X & Y你可以写MyIntersect<X, Y>定义为

type MyIntersect<T, U> =
    [T] extends [U] ? T :
    [U] extends [T] ? U :
    T & U;

这在您的示例代码中具有所需的行为:

function andI<T>(x: T): MyIntersect<T, I> { /* ... */ }
function andBase<T>(x: T): MyIntersect<T, Base> { /* ... */ }

let sub = new Sub();
let subAndI = andI(sub); // Has type Sub
let subAndBase = andBase(sub); // Has type Sub

Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBAsiCSA7YEBOBnCBjYAeAKgDRQCqAfFALwBQUdUA2vgLpQQAeKiAJuoyawD8UfFABctegwFtOEHnyZDS4yXVEAyUgG5q1AJbI0AMwCGWaPCgBvNVFQRT3APaIANiCj708MVGCoAK4Q1AC+elhupuh8AELR0Lb0UAD0KVAAEgCCAMIA0n4ZpgBuhgDmUKZQYKj6xaYoUAC2EE0ARmhQxs6oFnyIzk2Gpm7+4OV2NXUN0A5Orh5e6PGYfgHBVP5BELrJwM4AygHlABQAlDZ2yWmkmP4AFtBT9Y3G+hBu3P7OUACOge9gFAAO6mVCIcoAOiu9AcwEC4KgAANvCsIJQACTWYD3byQ1EJUJI3b0cLhaiRaJ8A6BNqyLi8KBorxNMBuVryYB8KxJWGOFzuTzeXxbDaUUUhck3HKBdD7JpjSBQMqBMG8aHGQKIHD6VxLeAndh+LUAawGwMQZz87CWUB5djhCMQiogzmMUBtlC9UAA5M42gArbDAH1QDRaG0AQm9iECblG4d9wtDhg9YY0MLohsqfBO1kYPuTzGNiDNzgtUFCZzODELPh9rC94vWOzCek12uAuudBMwhpLZYtVrT3iZCUuyUdiJthjlpm1rvdaN0UvSSBQGGDIP0OMqPcQxjQDi+oCVYDBphaG-QGq1Or18+48AIZH7ImHcHXaEwOAIxHgFC8nQ+juicwqGtWE7JHy8LTiSdChGwbh3EB0E4qg5ZQIgEDAlAACiqAYag5zwZWbbUB297Oo+aIvm++AfggRibr+RBjpggF2CBUBgcsCQQRcqGThAsHOuwpGIR8KGZg8GG4dhuEEURJF2GSejskC6C0psClQDSbQqRpUBaW0WQ8FY4qPgaJlnNoqTpEUfCntA+nUEZJlmdwzKWTwaInDZdk3I5Lp6bSejhUAA

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

为什么 TypeScript 不简化类型与其超类型之一的交集? 的相关文章

随机推荐

  • Prometheus 来源的时间序列:如何将空值设置为零?

    使用 Docker Grafana 8 1 5 使用时间序列图 我正在绘制Prometheus Counter来源 有一个label as a time series 按标签 并且需要将所有空 缺失值填充为零 这是应用于的查询Prometh
  • 这个采用类型对象的函数如何在 TS 中键入?

    我有一个接受键对象的函数 每个值都有一个类型 这样为每一个其中一个字段的类型决定了另一个字段的类型 代码 We have this Alpha type and echo function type NoInfer
  • 查找数据框中所有站点共有的日期[重复]

    这个问题在这里已经有答案了 我正在处理一个大型数据集 我想找到所有网站通用的日期 site lt c 1 1 1 2 2 2 2 3 3 4 4 4 date lt c 4th Nov 5th Nov 6th Nov 5th Nov 6th
  • jQuery UI 自动完成 - 如何获取用户输入的值

    免责声明 我已经浏览了相关问题 但找不到该特定问题的解决方案 场景是这样的 根据用户是否从下拉列表中选择了建议或者是否没有匹配项 我想执行不同的 jQuery ajax 操作 我该怎么做呢 我卡住的地方是如何捕获当前在自动完成输入文本框中的
  • 根据 ObjectId 聚合和减少嵌套数组

    我有一个Event文档结构如下 我正在尝试查询employeeResponses用于收集单个员工的所有响应 可能存在也可能不存在 的数组 eventDate 2019 10 08T03 30 15 000 00 00 employeeRes
  • 使用 HTML5 doctype 时忽略 CSS 文件

    我有一个 HTML 页面 当调整浏览器窗口大小时 它使用 CSS 和 JavaScript 将页面内容置于浏览器中间 当我使用标准 HTML5 Doctype 声明时 所有浏览器完全忽略我的 CSS 文件 我不知道为什么 当我删除 HTML
  • 创建 React 应用程序 - 如何将 pdf.worker.js 文件从 pdfjs-dist/build 复制到项目的输出文件夹?

    由于我无法在要使用该应用程序的网络中使用浏览器的 pdf 查看器 因此我正在测试反应 pdf https www npmjs com package react pdf用于使用 React 加载 PDF 的包 我制作了一个组件 用于发送从后
  • 如何在 SQL Server 中查找运行跟踪?

    有没有一种简单的方法来确定已设置的痕迹sp trace create在 SQL Server 2000 上 SQL Server 2005 2008 2012 或 2014 怎么样 SQL Server 2005 向前 SELECT FRO
  • 我可以在 Composer 中存在循环依赖吗?

    我正在编写一个包 A 它是其他一些包 B 所需要的 而我现在还没有发布该包 B 在某些时候 A 将被更改为使用自身 B 很可能它们应该在同一个包中 但我更喜欢将这两件事分开 只是为了干净起见 更重要的是 因为 B 只是对 A 的开发依赖 p
  • 片段未显示

    我遇到一个问题 我可以创建一个片段 它的视图似乎已创建 但没有显示 片段本身已创建 内部的任何代码都可以正常运行 但它只是在某处不可见 后退按钮也可以很好地与它交互 它 关闭 它 它只是没有实际显示在屏幕上 仅显示主布局 来自我的 Frag
  • 禁用 iPhone 4S / 新 iPad 键盘上的听写按钮

    我们的应用程序是一个医疗保健应用程序 我们的应用程序中有一个符合 HIPAA 标准的语音识别器 所有听写都可以通过它进行 医院不希望医生意外开始与不符合 HIPAA 标准的 Nuance Dragon 服务器进行对话 因此 我正在寻找可以抑
  • 我可以在 NHibernate 中使用表值函数作为查询源吗?

    正如您可能已经猜到的 亲爱的社区 我有一个问题要问您 所以 我希望 NHibernate 根据表值 sql 函数的评估来过滤查询结果 NHibernate 生成的可能的 SQL 查询可能类似于以下内容 SELECT whatever FRO
  • Android Studio 中出现错误:INSTALL_FAILED_OLDER_SDK

    我刚刚安装了 Android Studio 并正在从 Eclipse 移植我的项目 当我尝试仅在我的手机 4 4 2 API 19 上运行该应用程序时 出现错误Failure INSTALL FAILED OLDER SDK 我的 buil
  • UIScrollView 内视图中的 UIgestureRecognizer

    有没有人设法让 UIGestureRecognizer 在作为 UIScrollView 子视图的 UIView 上工作 我的回调似乎从未被调用 作为一个简单的示例 我想要一个分页滚动视图 并在第三页上使用 UITapGestureReco
  • Flexbox:带有粘性页脚的可滚动内容

    我想制作一个盒子 在本例中为弹性项目 它始终位于容器的中间 在该框中 有页眉 页脚和内容部分 如果内容的高度变得太大 我希望内容部分可以滚动 页眉和页脚应始终可见 并且框应始终保留在其容器中 这是我能够写的内容 HTML div class
  • 如何跟踪 SSIS 数据流任务中成功处理或失败的行的状态?

    我有一个非常简单的数据流任务从 FF 读取数据并将数据插入表中 同时我想在审计表中写入 插入了多少行 创建日期 我怎样才能轻松做到这一点 如果您只对成功处理的行数或遇到错误的行数感兴趣 那么您可以使用内置的SSIS logging特征 请检
  • 使用 Omni Thread Library 在 Delphi 中异步获取函数结果

    我试图从另一个单元 类调用一个函数 这需要一些时间来执行任务并返回一个字符串值 我找不到类似于 C async await 的好的参考 比如 Delphi 中的简单方法 使用 Omni Thread 库对我来说似乎是个好主意 一个简单的例子
  • 在子目录(而不是根目录)中运行 Wordpress

    我有一个wordpress网站 当前运行在我网站的子目录中 基本上我的结构如下 根 wp 我想将 wordpress wp 文件夹保留在同一位置 但让它直接从根 url 加载 wordpress 网站 例如 当前当我访问 www mysit
  • 用于分析 Node.js 核心转储的工具 [已关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 如果我使用 gcore 制作 Node js 进程的代码转储 分析它的最佳工具是什么 灵感来自 jav
  • 为什么 TypeScript 不简化类型与其超类型之一的交集?

    有没有办法让 TypeScript 的检查器简化交集类型中不必要的元素 或者我错认为它们是不必要的 IIUC 类型SubType SuperType相当于SubType 但 typescript 似乎没有执行这种简化 如下所示 我定义了一个