为啥联合类型中的“从不”类型毫无意义?

Posted

技术标签:

【中文标题】为啥联合类型中的“从不”类型毫无意义?【英文标题】:Why is the type "never" meaningless in union types?为什么联合类型中的“从不”类型毫无意义? 【发布时间】:2021-01-21 14:50:00 【问题描述】:

Typescript playground

我在试图理解 never 类型毫无意义并且可以在 联合类型中被丢弃这一事实时遇到了一些麻烦。

我的意思是,我在 intersection 类型 中得到它,never 将立即使所有结果都生成 never,因为没有任何类型可以通过某种类型和 never 在同一类型。这对我来说很有意义。

但在联合类型中,我的直觉最初告诉我never 类型将是一个有效的选项。我的问题是为什么不是?为什么never可以在联合类型中被丢弃?

谁能对此给出合理的解释,以便我更好地理解它?

【问题讨论】:

如果你使用集合而不是类型,那么string & never = never 就像A ∩ ∅ = ∅(其中 是空集),string | never = string 就像A ∪ ∅ = A。或者,如果您使用的是逻辑命题,string & never = never 类似于 A ∧ ⊥ = ⊥(其中 为“假”),string | never = string 类似于 A ∨ ⊥ = A。或者如果你真的眯着眼睛使用乘法和加法,那么string & never = never 就像a × 0 = 0string | never = string 就像a + 0 = a。你想把这个比喻作为答案吗?或者您对下面 VLRoyrenn 的回答满意吗? @jcalz:应该是一个答案。也打算用集合来解释它。 【参考方案1】:

认为类型的一种方法是将所有可分配给它的值的set。所以boolean 可以被认为是true, false,这个集合只包含这两个值。而string 可以被认为是(本质上)包含所有可能的string 值的无限集。

在 TypeScript 中,never 是 bottom type。它有 no 值。如果你有一个 javascript 值并且你问“这是一个 never 类型的值吗?”那么答案是“不”。在集合方面,never 可以被认为是∅, empty set.

在从类型到值集的映射中,TypeScript 中的intersection 操作(&)可以被认为是set intersection 操作(∩)。如果你有集合 A 和 B,那么 A∩B 是恰好是 both A and B 成员的对象的集合。对于任何集合 A,交集 A带有空集的∩∅就是空集∅。 A 和空集中都没有元素,因为空集中根本没有元素。回到 TypeScript 类型,这意味着对于任何类型 AA & never 变为 never。如果 TypeScript 编译器只是将 string & never 保留为 string & never,这将是有效的,但实际上它会继续并自动将其缩减为 never,因为后者的表示更简单。

另一方面:在从类型到值集的映射中,TypeScript 中的 union 操作 (|) 可以被认为是 set union 操作 (∪)。如果你有集合 A 和 B,那么 A∪B 是恰好是 either A B 成员的对象的集合(这是一个 inclusive 或)。对于任何集合 A,与空集的并集 A∪∅ 就是 A。并集包含 A 的所有元素和空集的所有元素。由于没有空集的元素,那只是“A 的所有元素”。回到 TypeScript 类型,这意味着对于任何类型 AA | never 变为 A。如果 TypeScript 编译器只是将 string | never 保留为 string | never,这将是有效的,但实际上它会继续并自动将其缩减为 string,因为后者的表示更简单。

这就是基本的解释。还有其他类比,例如布尔逻辑命题,例如“此元素是此类型的成员”,对于 never 类型始终为 FALSE,导致 A ∧ FALSE = FALSE 和 A ∨ FALSE = A 之类的事情。或者像算术,其中类比不精确,但交集看起来像乘法,而并集看起来像加法(这个类比对于对而不是交集和区分联合而不是常规联合来说变得精确)并且never 类型是 0。但希望这个对编译器为什么会这样表现给出了足够的直觉。

请注意,TypeScript 中还有一个名为 unknown 的 top type,其行为与 never 的对偶在 A & unknown = AA | unknown = unknown 中完全相同,并且在集合论中具有对偶模拟(universal set/class) .但是你没有问这个问题,这个答案已经足够长了。

【讨论】:

感谢您的详细解释。现在这对我来说完全有意义。我认为我错了是因为我认为 string 是“实际的字符串类型”,而不是所有可能的 string 类型的表示。【参考方案2】:

具体解释,type UNION_A = string | boolean | never 的变量可以为boolean 的任何有效值、string 的任何有效值和never 的任何有效值(根据定义,其中不存在任何值),因此,never 类型的联合不会向该变量可能最终接收的值域添加任何内容。

编辑:never 类型的要点是它是不可能发生的事情的类型,通常是不可能返回的函数的返回类型。

function fail() 
    throw new Error(":(");
    // What is the *return* type of that function?


let foo = fail(); // foo can only receive the type never
console.log(foo * 2); // Doesn't matter what foo is, this is guaranteed to be dead code

fail() 不可能返回值,甚至 null 也不可能。它可以永远返回,所以它的返回类型是never,任何使用该函数的返回值操作的代码基本上都是死代码。

与 never 的联合通常意味着你拥有这个

function do_thing(x) 
    let foo;
    if (x == 0) 
        fail(); // foo is of type never
     else 
        foo = 1/x; // foo is of type number
    

    // Foo is technically of type number | never,
    // but you can discard never since it never runs.
    let bar = foo * 2;
    return bar;

【讨论】:

这是一个很好的答案,但有一个问题。 never 的返回类型不是从 throw 语句中推断出来的。 @AluanHaddad:不是在 Typescript 中吗?我见过在许多其他情况下从未被推断出,而且我知道不同的功能通常是该类型的常见用例,但这实际上令人惊讶。为我服务,无需仔细检查。 这种情况很奇怪。返回类型的推断通常比显式注释更精确,我在大多数情况下都会利用它。 什么是fail()Exception() 是什么?当您的代码在 IDE 中编译时,它会有所帮助……但 this 有错误。您介意在此处编辑代码以使其按照您描述的方式工作吗? @jcalz:我的错,我一直忘记 JS 的默认异常类是 Error。修复了代码,使其至少是有效的 JS/TS。

以上是关于为啥联合类型中的“从不”类型毫无意义?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当 XMPP 连接断开时,出席类型用户从不可用变为可用

为啥 TypeScript 在使用 concat 减少数组时会推断出“从不”类型?

为啥打字稿将联合中的属性标记为不存在?

C中的void类型

打字稿数组混淆 - TS 2488 类型为“从不”

( 11 )MySQL中的联合查询