为啥 && 运算符会产生第二个操作数的类型
Posted
技术标签:
【中文标题】为啥 && 运算符会产生第二个操作数的类型【英文标题】:Why does the && operator produce the type of the second operand为什么 && 运算符会产生第二个操作数的类型 【发布时间】:2012-09-23 12:18:42 【问题描述】:TypeScript 规范在 §4.15.6 中说明了 &&
运算符:
&& 运算符允许操作数为任何类型,并产生与第二个操作数相同类型的结果。
在 javascript 中,&&
运算符如果为假则返回第一个操作数,否则返回第二个操作数 (see ECMA-262 §11.11)。
这意味着如果左操作数是假的,&&
将返回一个与左操作数类型匹配的值。例如,
typeof ( false && ) === "boolean" // true
typeof ( '' && 1 ) === "string" // true
typeof ( null && "hello" ) === "object" // true
typeof ( NaN && true ) === "number" // true
根据上面引用的规则,Typescript 会错误地预测上述表达式的类型分别为Object
、Number
、String
和Boolean
。 p>
我错过了什么吗?是否有充分的理由使 &&
表达式的类型与第二个操作数的类型相匹配?结果类型不应该像||
操作符一样,返回两个操作数中最好的通用类型,如果没有最好的通用类型则返回Any
?
【问题讨论】:
||
的类型是什么? a ? b : c
呢?如果我理解正确,a && b
等同于a ? a : b
(除了 a 在后一个示例中被评估两次)
@ReyCharles &&
就像 !a ? a : b
和 ||
就像 a ? a : b
。
对,对不起。那是脑残。我真正的问题是a || b
和a ? b : c
的类型是什么。有趣的问题,顺便说一句。
@ReyCharles TypeScript 说a || b
的类型是Any
,除非两个操作数有匹配的类型。在a ? b : c
中,如果我没记错的话,TypeScript 要求b
和c
是同一类型。
更新:如果 strictNullChecks
被启用,这现在可以在 Typescript 2 中正常工作。
【参考方案1】:
长话短说,这里没有让所有人都满意的解决方案。
考虑这个常见的成语:
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
放弃并决定 'address' 是 'any' 是很糟糕的,因为在 Customer 和 Address 之间没有最好的通用类型。
在使用 && 运算符的大多数情况下,类型 已经 匹配,或者 && 以像上面那样的值合并方式使用。无论哪种情况,返回正确操作数的类型都会为用户提供预期的类型。
虽然此时类型安全在技术上已经崩溃,但它并没有以可能导致错误的方式这样做。要么您要测试结果值的真实性(在这种情况下,类型或多或少无关紧要),要么您将使用推定的右操作数进行某些操作(上面的示例两者都做)。
如果我们查看您列出的示例并假装左操作数不确定是真还是假,然后尝试编写可对返回值进行操作的合理代码,它就会变得更加清晰 - 您可以做的事情并不多做 'false && ' 尚未进入“任何”参数位置或真实性测试。
附录
由于有些人不相信上述内容,这里有一个不同的解释。
让我们暂时假设 TypeScript 类型系统添加了三个新类型:Truthy<T>
、Falsy<T>
和 Maybe<T>
,表示 T
类型可能的真值/假值。这些类型的规则如下:
Truthy<T>
的行为与 T
完全相同
您无法访问Falsy<T>
的任何属性
Maybe<T>
类型的表达式,当用作if
块中的条件时,在同一if
块的主体中变为Truthy<T>
,在else
块中变为Falsy<T>
这会让你做这样的事情:
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!
到目前为止还不错。现在我们可以弄清楚 && 运算符的类型规则是什么。
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
类型,则它是虚假的,并且不安全。
您应该可以将其用作Maybe<U>
,因为Falsy<T>
和Falsy<U>
具有相同的行为
您不应该使用U
的属性,因为该值仍然可能是虚假的。
如果您在 if
测试中使用它,那么它应该成为该 if
语句块中的 Truthy<U>
换句话说,Falsy<T> XOR Maybe<U>
是 Maybe<U>
。它遵循所有相同的规则。您根本不需要通过添加这个奇怪的XOR
类型来使类型系统复杂化,因为已经存在适合您需要的所有规范的类型。
这有点像给某人一个盒子,然后说“这要么是一箱空垃圾箱,要么是一整箱可回收物”。您可以安全地将盒子中的物品清空到回收箱中。
【讨论】:
但这真的不是它的工作方式......如果你写alert(typeof(false && 'hello'))
,你会得到'boolean'
,而不是'string'
。事实上,该打字稿代码编译成相同的 javascript。您是否声称 && 运算符的行为与 javascript 不完全相同?
&& 做什么 && 做什么,TypeScript 与否。问题是当参数的类型不同时,结果的 compile-time 类型应该是什么。在 "false && 'hello'" 的情况下,合理的选项是 bool、string 和 any;上面的解释是为什么选择'string'而不是'any'或'bool'的类型。
谢谢,这是有道理的。它仍然让我对编译时类型准确性感到不安,但我想这里的实用价值胜过技术正确性。
@chuckj & Ryan Cavanaugh,你是说为了编译的目的,它假定右手操作数的类型......明白了。这让我非常不舒服......正如彼得所说,它扰乱了我对编译时准确性的感受。就是感觉不对。 (尽管老实说,我觉得强类型的 javascript 是错误的。)
抱歉,这绝对是疯了。类型系统应该提供保证,而不是一些模糊准则的盗版代码。一个正确的答案(即,一个不会抛弃类型系统的全部观点的答案)是拥有两组逻辑运算符。一个,&& 和 ||,用于 JavaScript 的“我们不需要 steenkin 类型”的世界,结果类型为 any
,另一个,比如 &&& 和 |||,用于 TypeScript,它们具有健全的语义和有用的类型规则. TypeScript 故意破坏了如此基本的东西,这让我感到非常震惊。【参考方案2】:
没有维护语言规范,在这种情况下是错误的。 &&
运算符的结果类型是右侧的类型和左侧的假值的并集。
来自TypeScript 2.0 release notes:
// Compiled with --strictNullChecks
declare function f(x: number): string;
let x: number | null | undefined;
// ..
let b = x && f(x); // Type of b is string | 0 | null | undefined
【讨论】:
以上是关于为啥 && 运算符会产生第二个操作数的类型的主要内容,如果未能解决你的问题,请参考以下文章