为什么&&运算符生成第二个操作数的类型

Pet*_*son 31 types typescript

TypeScript规范在§4.15.6中说明了&&运算符:

&&运算符允许操作数为任何类型,并产生与第二个操作数相同类型结果.

在Javascript中,&&运算符返回第一个操作数(如果它是假的),否则它返回第二个操作数(参见ECMA-262§11.11).

这意味着如果左操作数是假的,&&将返回一个与左操作数的类型匹配的值.例如,

typeof ( false && {}      ) === "boolean" // true
typeof ( ''    && 1       ) === "string"  // true
typeof ( null  && "hello" ) === "object"  // true
typeof ( NaN   && true    ) === "number"  // true
Run Code Online (Sandbox Code Playgroud)

打字稿,根据上面引述的规则,将不正确地预测的类型上面的表达式的是Object,Number,StringBoolean,分别.

我错过了什么吗?是否有充分的理由使&&表达式的类型与第二个操作数的类型相匹配?结果类型不应该像||运算符一样,并返回两个操作数的最佳公共类型,Any如果没有最佳公共类型?

Rya*_*ugh 25

长话短说,这里没有解决方案让大家高兴.

考虑这个常见的习语:

var customer = GetCustomer(...); // of type 'Customer'
var address = customer && customer.address;
if(address) {
    printAddressLabel(address); // Signature: (Address) => void
} else {
    // Couldn't find the customer or the customer has no address on file
}
Run Code Online (Sandbox Code Playgroud)

放弃并决定'地址'是'任何'是非常蹩脚的,因为客户和地址之间没有最佳的共同类型.

在使用&&运算符的大多数情况下,类型已经匹配,或者&&正在以类似于上面的值合并方式使用.在任何一种情况下,返回右操作数的类型都会为用户提供预期的类型.

虽然此时类型安全在技术上已经崩溃,但它并没有以可能导致错误的方式进行.您要么测试结果值的真实性(在这种情况下类型或多或少不相关),或者您将使用假定的右操作数进行某些操作(上面的示例同时执行这两个操作).

如果我们看一下你列出的例子,并假装左操作数是不确定的truthy或falsy,然后尝试编写对返回值进行操作的合理代码,它会变得更加清晰 - 你可以用'false && 多少{}'尚未进入'任何'参数位置或真实性测试.


附录

由于有些人不相信上述内容,这里有不同的解释.

让我们假装的打字稿类型系统增加了三种新类型的一个时刻:Truthy<T>,Falsy<T>,和Maybe<T>,代表类型的可能truthy/falsy值T.这些类型的规则如下:

  1. Truthy<T> 表现得很像 T
  2. 您无法访问任何属性 Falsy<T>
  3. 类型的表达式Maybe<T>,作为条件在使用时if块,成为Truthy<T>在同一的主体if块和Falsy<T>else

这可以让你做这样的事情:

function fn(x: Maybe<Customer>) {
   if(x) {
      console.log(x.address); // OK
   } else {
      console.log(x.phone); // Error: x is definitely falsy
   }
   console.log(x.name); // Warning: x might be falsy!
}
Run Code Online (Sandbox Code Playgroud)

到目前为止相当不错.现在我们可以弄清楚&&运算符的类型规则是什么.

  • Truthy<T> && x 应该是一个错误 - 如果左侧被认为是真的,你应该写的 x
  • Falsy<T> && x应该是一个错误 - 如果已知左侧是假的,x则是无法访问的代码
  • Maybe<T> && x 应该产生什么?

我们知道结果Maybe<T> && x将是类型的假值T,或者x.它不能产生Truthy<T>(除非T= = x在这种情况下整个讨论都没有实际意义的类型).我们称之为新类型Falsy<T> XOR Maybe<U>.

规则应该Falsy<T> XOR Maybe<U>是什么?

  • 显然,你不能使用T它的属性.如果值是类型T,它是假的,并且不安全使用.
  • 您应该能够将其用作a Maybe<U>,因为Falsy<T>Falsy<U>具有相同的行为
  • 您不应该使用属性U,因为值仍然可能是假的.
  • 如果您在if测试中使用它,那么它应该成为Truthy<U>if语句的块

换句话说,Falsy<T> XOR Maybe<U> Maybe<U>.它遵循所有相同的规则.通过添加这种奇怪的XOR类型,您根本不需要使类型系统复杂化,因为适合您所需的所有规范的类型已经存在.

这有点像给某人一个盒子并说"这可能是一个空盒子的垃圾,也可能是一整箱可回收物品".您可以安全地将盒子的内容清空到回收站中.

  • &amp;&amp; 的作用与 &amp;&amp; 的作用相同,无论是否为 TypeScript。问题是当参数类型不同时,结果的*编译时*类型应该是什么。对于“false &amp;&amp; 'hello'”,合理的选项是 bool、string 和 any;上面的解释是为什么选择“string”而不是“any”或“bool”类型。 (2认同)
  • 谢谢,这是有道理的.它仍然会扰乱我对编译时类型精度的敏感性,但我认为实用价值胜过技术正确性. (2认同)
  • 对不起,这绝对是疯子.类型系统应该提供保证,而不是模糊指南的一些盗版代码.一个正确的答案(即,不会抛弃类型系统的整个点)的答案是拥有两组逻辑运算符.一,&&和||,对于JavaScript的"我们不需要没有steenkin'类型"世界,结果类型为"any",而一个,比如说&&&和|||,对于TypeScript,它既有合理的语义,又有用的类型规则.我非常惊讶于在TypeScript中刻意破坏了一些如此根本的东西. (2认同)