【TS】TypeScript 实践中的 Equals 是如何工作的?

news2024/12/23 14:44:33

How does the Equals work in typescript

desc

循着线索慢慢来

在 ts 中如何判断两种类型完全一致?

三年前,在社区有一场关于支持 type level equal operator 的讨论 TypeScript#27024。

大佬 @mattmccutchen 给出了一个非常精彩的解决方案:

Here’s a solution that makes creative use of the assignability rule for conditional types, which requires that the types after extends be “identical” as that is defined by the checker:

export type Equals<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

This passes all the tests from the initial description that I was able to run except H, which fails because the definition of “identical” doesn’t allow an intersection type to be identical to an object type with the same properties. (I wasn’t able to run test E because I don’t have the definition of Head.)

它本人并没有给出任何关于这个类型工作原理的解释,但它确实非常 work,在实践中被大量使用。

不过,在后面其他人的交流中,发现了一些可能对理解有帮助的 comment。

@fatcerberus

@jituanlin AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:

  • Both conditional types have the same constraint
  • The true and false branches of both conditions are the same type

这个类型在做的事情实际上就是,对 <T>() => T extends X ? 1 : 2<T>() => T extends Y ? 1 : 2 做 assignability 检查。

而这个针对 conditional type 的检查,仅当下面两点满足时,才认为前者 assignable to 后者。

  • XY 一致
  • conditional type 各自的两个分支相应位置一致

但我不太确定他的所谓的 “一致”(same) 具体是什么含义。

后面,还有一条更有帮助的 comment:

@tianzhich

@jituanlin AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:

  • Both conditional types have the same constraint
  • The true and false branches of both conditions are the same type

where can I find the infomations about the internal ‘isTypeIdenticalTo’ check? I can’t find anything in the typescript official website…

I found this in /node_modules/typescript/lib/typescript.js, by searching isTypeIdenticalTo. There are also some comments that may help someone here:

// Two conditional types ‘T1 extends U1 ? X1 : Y1’ and ‘T2 extends U2 ? X2 : Y2’ are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.

image

But I’m still not very clear what the related mean here? I can’t understand the src code of isRelatedTo.

在 hash [f1ff0de] - src/compiler/checker.ts 中确实找到了这个注释:

desc

它给出了对 conditional type 进行 assignability check 的更细致的说明:

它要求:

  • sourceType1 和 sourceType2 只要存在任意方向的 assignable 关系即可。

    例如 1number{ foo: number, bar: string }{ bar: string } 都是可以的。

    Record<PropertyKey, unknown> 和 tuple type [] 不行,stringnumber 也不行。

  • extendFromType1 和 extendsFrom2 必须是"完全一致"(identical)的。

  • canExtendBranchType1 is assignable to canExtendBranchType2。

  • cannotExtendBranchType1 is assignable to cannotExtendBranchType2。

经过测试,注释中的 ‘related’ 指的是 ‘x assignable to y’ 的关系。

这个关系过于细节,并非通过直觉就能推断出来的,所以 Equals 实际上是一个非常 hack 的实现。

好了,道理我都懂,Equals 到底怎么工作的?

我们回头研究 Equals 的实现。

使用 generic function 的目的

我看到 Equals 的第一反应,是疑惑为什么长得这么怪,相等性判断为什么会跟函数扯上关系?

其实是否是函数并不重要,重要的是,我们需要在 Equals 的上下文中使用一个未被指定的 generic type T 来构成一个 conditional type。

实际上这个 generic function 从头到尾就没被实例化过,它的作用仅仅是提供一个可能为任意类型的 generic T

注意,我这里提到的任意类型与 any 并不是一个概念,any 基本上是所有类型的全集的概念,而任意类型则是全集中的任意集合的概念。

下文同。

所以,从这个角度来看,内部的 conditional type 与它在 function 中的位置并无关系,我们把它放在参数位置也是可以的:

type Equals<X, Y> =
  (<T>(arg: T extends X ? 1 : 2) => any) extends
  (<T>(arg: T extends Y ? 1 : 2) => any) ? true : false;

conditional type 是如何安排的

type Equals<X, Y> =
	(<T>() => T extends X ? 1 : 2) extends
	(<U>() => U extends Y ? 1 : 2) ? true : false

第二个 generic T 换成 U 是为了提醒,两个 conditional type 中的 T 基本上无任何关系。

套用我们刚刚了解到的关于 conditional type 之间的 assignability 检查规则来看。

  • TU 只要 the one reated to other 即可,而它们是任意类型,所以它们并不重要,也不需要考虑。

  • XY 必须是完全一致的,这就是这个解决方案的核心 hack 点,利用 ts checker 对 conditional type 进行 assignbility check 的机制,将 XY 放在正确的位置,从而让 checker 对 X Y 进行了"完全一致"的这种相等性判断。

  • 至于 12,它们只要满足对应位置上有正方向的 assignable 关系即可 —— 即 1 extends 12 extends 2

    所以 12 本身并不重要,我们可以根据上面的规则轻易构造出其他的例子。

    但还要注意的是,我们必须保证 1 位置上的类型 not related to 2 位置上的类型 ,才能让 Equals 在结果应该为 false 上的 case 也正常工作。

    例如下面这几个 case 都是 work 的:

    type Equals1<X, Y> =
      (<T>() => T extends X ? 1 : '1') extends 
      (<U>() => U extends Y ? number : string) 
      ? true : false
    
    type Equals2<X, Y> =
      (<T>() => T extends X ? { foo: number } : 2) extends
      (<U>() => U extends Y ? { foo: number, bar?: string } : {}) 
      ? true : false;
    
    // 这个 case 也是 work 的,想想为什么?
    type Equals3<X, Y> = 
      (<T>() => T extends X ? T : T) extends
      (<U>() => U extends Y ? U : U) 
      ? true : false;
    

基本上就是这样,这应该是 @mattmccutchen 构造 Equals 时脑子里的冰山一角,更多的应该是 TypeScript 的实现,他对 checker 基本了如指掌才会有如此功力,而不是想我这样从注释中管中窥豹。

而即便如此,我也花了断断续续大约 20+ 有效思考小时,才勉强弄明白他的结构,以及各部分在这个功能中负责做什么。

还有一个可能对理解有帮助的来自爆栈网的解释,附在文末。

下面是用到的 test cases,大家可以拿去自己把玩一下。

test cases

type Except<T extends U, U> = T
type Head<T extends any[]> = T extends [infer F, ...infer _] ? F : never;

type cases = [
  Except<Equals<1, 2>, false>,
  Except<Equals<{ foo: number }, { foo: string }>, false>,
  Except<Equals<{ foo: number }, { foo: number, bar: string }>, false>,
  Except<Equals<{ foo: number }, { foo?: number }>, false>,
  Except<Equals<{ foo: number }, { foo: number }>, true>,
  Except<Equals<'a', 'a' | 'b'>, false>,
  Except<Equals<never, never>, true>,
  Except<Equals<'a', 'a'>, true>,
  Except<Equals<string, number>, false>,
  Except<Equals<1, 1>, true>,
  Except<Equals<any, 1>, false>,
  Except<Equals<1 | 2, 1>, false>,
  Except<Equals<Head<[1, 2, 3]>, 1>, true>,
  Except<Equals<any, never>, false>,
  Except<Equals<never, any>, false>,
  Except<Equals<[any], [never]>, false>,
]

ref

  1. Github - TypeScript#27024
  2. 爆栈网 - How does the Equals work in typescript?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智公网:教师编的这些规则要知道!

1、有了教师资格证&#xff0c;是否还需要考取编制&#xff1f; 答&#xff1a;有了教师资格证之后是需要继续教师编制考试的。只有通过了教师编制考试才能有教师编&#xff0c;只有一个教师资格证&#xff0c;只能证明是具备了从业资格。通过教师编制的人员被称为在编人员&am…

【Web开发】Python实现Web图表功能(D-Tale编译打包)

&#x1f37a;基于Flask实现服务器的相关文章如下&#x1f37a;&#xff1a; &#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask快速入门&#xff09;&#x1f388;&#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask案例测试&#xff09;&#x1f3…

MySQL中SQL语句执行顺序及优化

概要 本文章主要是分析SQL语句关键字的执行顺序&#xff0c;以及在每一个阶段我们有哪些优化&#xff0c;可以去做哪些优化&#xff0c;和注意事项。 1. SQL语句关键字的执行顺序 通常我们执行一条SQL语句它的执行顺序如下 selectfrom.joinwheregroup byhavingorder by聚合函…

Vitepress(二):部署

什么是Git Page github Pages可以被认为是用户编写的、托管在github上的静态网页。 github pages有300M免费空间&#xff0c;资料自己管理&#xff0c;保存可靠&#xff1b; 实现项目自动推送到Github 首先新建一个自己的项目用于存放github pages的内容 格式是 自己githu…

《元宇宙2086》影视工业弯道超车?《科普时报》刊登采访报道

科普时报-第267期 2023年01月06日 星期五 第05版&#xff1a;书香文史刊载了题目为“《元宇宙2086》影视工业弯道超车&#xff1f;”的关于高泽龙的采访报道。全文内容如下&#xff1a;在2022年中国金鸡百花电影节暨第35届中国电影金鸡奖期间&#xff0c;我创作的中国首部元宇宙…

Linux开发工具的使用(二)

文章目录Linux开发工具的使用&#xff08;二&#xff09;1.Linux编译器gcc/g使用1.1 背景1.2 验证每一个阶段的效果1.2.1 预处理1.2.2 编译1.2.3 汇编1.2.4 链接1.2.5 记忆1.3 链接的理解1.3.1 ldd指令1.3.2 预备1.3.3 动态库和静态库感性理解到实际理解2.Linux项目自动化构建工…

吴恩达《机器学习》——SVM支持向量机

SVM支持向量机1. 线性SVM1.1 从Logistic回归出发1.2 大边界分类与SVM1.3 调整正则化参数2. 非线性SVM&#xff08;高斯核函数&#xff09;2.1 高斯核2.2 非线性分类2.3 参数搜索数据集、源文件可以在Github项目中获得 链接: https://github.com/Raymond-Yang-2001/AndrewNg-Mac…

Java编程基础

1&#xff0c;基本概念 &#xff08;1&#xff09;JDK、JRE、JVM的关系&#xff1a; JDK&#xff1a;Java Development Kit&#xff0c;Java开发工具包JRE: Java Runtime Environment&#xff0c;Java运行环境JVM&#xff1a;Java Virtual Machine&#xff0c;Java虚拟机JDK包…

2023年山东最新建筑八大员(电气施工员)模拟真题题库及答案

百分百题库提供建筑八大员&#xff08;电气&#xff09;考试试题、建筑八大员&#xff08;电气&#xff09;考试预测题、建筑八大员&#xff08;电气&#xff09;考试真题、建筑八大员&#xff08;电气&#xff09;证考试题库等,提供在线做题刷题&#xff0c;在线模拟考试&…

【UE4 第一人称射击游戏】22-拾取弹药

上一篇&#xff1a;【UE4 第一人称射击游戏】21-添加动态扩散准心本篇效果&#xff1a;当角色触碰到弹药箱后&#xff0c;玩家的后备弹夹就会多50发子弹&#xff0c;并且触碰到弹药箱后&#xff0c;会播放相应的声音和粒子特效。步骤&#xff1a;新建一个蓝图类&#xff08;父类…

MySQL-5.7 innodb在线DDL操作(增删改索引、列、外键、表、外键)

基本概念 在开始阅读前&#xff0c;先熟悉下以下概念&#xff0c;以便更加方便理解。 DML DML&#xff08;Data Manipulation Language&#xff09;数据操作语言-数据库的基本操作&#xff0c;SQL中处理数据等操作统称为数据操纵语言,简而言之就是实现了基本的“增删改查”操作…

jenkins中错误总结

每次使用jenkins都会遇到不同的bug&#xff0c;接下来我们看一下这几个 libXrender.so.1: cannot open shared object file: No such file or directory 接下来我们看一下解决方案,一步一步安装好就可以了 yum install ksh -y yum install libXext.so.6 -y yum install libX…

案例分析 - 考查点总览

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 案例分析 - 总览知识点 往年案例一览 一般情况下&#xff0c;往下数5、6年的题目出题形式&#xff0c;具有参考意义 年份试题一试题二试题三试题四试题五2022年系统分析与建模[结构化与面向对象分析、用例…

MySQL (四)------DML操作表记录-增删改【重点】DQL操作表记录-查询【重点】

DML操作表记录-增删改【重点】 准备工作: 创建一张商品表(商品id,商品名称,商品价格,商品数量.) create table product(pid int primary key auto_increment,pname varchar(40),price double,num int ); 1.1 插入记录 1.1.1 语法 方式一: 插入指定列, 如果没有把这个列进行列…

联合证券|2022年逾200家企业IPO“撤单”,谁在“临阵脱逃”?

刚刚过去的2022年&#xff0c;A股IPO募集资达5870亿元&#xff0c;创下前史新高。不过&#xff0c;也有不少“带病闯关”的IPO项目遇阻&#xff0c;计算数据显现&#xff0c;2022年A股IPO共有234家企业撤单&#xff0c;创9年以来最大撤回潮。 谁在惊惶万状&#xff1f; 2022年…

LeetCode[剑指offer 40]最小的k个数

难度&#xff1a;简单 题目&#xff1a; 输入整数数组 arr &#xff0c;找出其中最小的 k 个数。例如&#xff0c;输入4、5、1、6、2、7、3、8这8个数字&#xff0c;则最小的4个数字是1、2、3、4。示例 1&#xff1a; 输入&#xff1a;arr [3,2,1], k 2 输出&#xff1a;[…

C 语言的 互斥锁、自旋锁、原子操作

今天不整 GO 语言&#xff0c;我们来分享一下以前写的 C 代码&#xff0c;来看看 互斥锁&#xff0c;自旋锁和原子操作的 demo 互斥锁 临界区资源已经被1个线程占用&#xff0c;另一个线程过来访问临界资源的时候&#xff0c;会被CPU切换线程&#xff0c;不让运行后来的这个线…

基础算法(二)——归并排序

归并排序 介绍 归并排序是一种复杂度O(nlog(n)nlog(n)nlog(n))的排序算法&#xff0c;并且在任何情况下都是&#xff0c;但是它不是原地算法&#xff0c;即需要额外存储空间 其原理是&#xff0c;先将区间均匀分成左右两半&#xff0c;然后再对左右两半继续二分&#xff0c;…

Large Language Models Are Reasoning Teachers

Paper name Large Language Models Are Reasoning Teachers Paper Reading Note URL: https://arxiv.org/pdf/2212.10071.pdf twitter 宣传&#xff1a; https://twitter.com/itsnamgyu/status/1605516353439354880 TL;DR 提出了 Fine-tune-CoT 方法&#xff0c;旨在利用非…

Java --- JVM对象内存布局与访问定位

目录 一、对象内存布局 1.1、对象头(Header) 1.1.1、运行时元数据(Mark Word) 1.1.2、 类型指针 1.2、实例数据(Instance Date) 1.3、对齐填充(Padding) 二、对象访问定位 一、对象内存布局 1.1、对象头(Header) 1.1.1、运行时元数据(Mark Word) 1、哈希值(HashCode) 2、G…