Por que o operador && produz o tipo do segundo operando
-
12-12-2019 - |
Pergunta
A especificação TypeScript afirma em §4.15.6 sobre o &&
operador:
O operador && permite que os operandos sejam de qualquer tipo e produz um resultado do mesmo tipo que o segundo operando.
Em Javascript, o &&
operador retorna o primeiro operando se for falso, caso contrário retorna o segundo operando (consulte ECMA-262 §11.11).
Isso significa que se o operando esquerdo for falso, &&
retornará um valor que corresponde ao tipo do operando esquerdo.Por exemplo,
typeof ( false && {} ) === "boolean" // true
typeof ( '' && 1 ) === "string" // true
typeof ( null && "hello" ) === "object" // true
typeof ( NaN && true ) === "number" // true
O texto datilografado, de acordo com a regra citada acima, seria incorretamente prever os tipos das expressões acima como sendo Object
, Number
, String
e Boolean
, respectivamente.
Estou esquecendo de algo?Existe uma boa razão para fazer o tipo de &&
expressão corresponde ao tipo do segundo operando?O tipo de resultado não deveria se comportar como o ||
operador e retornar o melhor tipo comum dos dois operandos, e Any
se não houver melhor tipo comum?
Solução
Resumindo, não há solução aqui que agrade a todos.
Considere este idioma comum:
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
}
Seria muito chato desistir e decidir que 'endereço' é 'qualquer' porque não existe um tipo comum entre Cliente e Endereço.
Na maioria dos casos em que o operador && é usado, os tipos já match, ou && está sendo usado de maneira aglutinadora de valores, como acima.Em ambos os casos, retornar o tipo do operando correto fornece ao usuário o tipo esperado.
Embora a segurança de tipo esteja tecnicamente falhando neste ponto, ela não está fazendo isso de uma forma que possa resultar em um erro.Ou você testará a veracidade do valor resultante (nesse caso, o tipo é mais ou menos irrelevante) ou usará o presumível operando correto para alguma operação (o exemplo acima faz as duas coisas).
Se olharmos para os exemplos que você listou e fingirmos que o operando esquerdo é indeterminadamente verdadeiro ou falso e então tentarmos escrever um código sensato que operaria no valor de retorno, ficará muito mais claro - simplesmente não há muito que você possa fazer com 'false && {}' que ainda não está entrando em uma posição de argumento 'qualquer' ou teste de veracidade.
Termo aditivo
Como algumas pessoas não ficaram convencidas com o que foi dito acima, aqui está uma explicação diferente.
Vamos fingir por um momento que o sistema de tipos TypeScript adicionou três novos tipos: Truthy<T>
, Falsy<T>
, e Maybe<T>
, representando possíveis valores verdadeiros/falso do tipo T
.As regras para esses tipos são as seguintes:
Truthy<T>
se comporta exatamente comoT
- Você não pode acessar nenhuma propriedade de
Falsy<T>
- Uma expressão do tipo
Maybe<T>
, quando usado como a condição em umif
bloco, torna-se umTruthy<T>
no corpo desse mesmoif
bloco e umFalsy<T>
noelse
bloquear
Isso permitiria que você fizesse coisas assim:
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!
}
Muito bom até agora.Agora podemos descobrir quais são as regras de tipo para o operador &&.
Truthy<T> && x
deveria ser um erro - se o lado esquerdo for verdadeiro, você deveria ter escritox
Falsy<T> && x
deveria ser um erro - se o lado esquerdo for falso,x
é um código inacessívelMaybe<T> && x
deveria produzir...o que?
Sabemos o resultado de Maybe<T> && x
será um valor falso do tipo T
, ou x
.Não pode produzir Truthy<T>
(a menos que T
== o tipo de x
nesse caso, toda essa discussão é discutível).Vamos chamar esse novo tipo Falsy<T> XOR Maybe<U>
.
Quais devem ser as regras de Falsy<T> XOR Maybe<U>
ser?
- Claramente, você não pode usar propriedades de
T
nele.Se o valor for do tipoT
, é falso e não é seguro para uso. - Você deve ser capaz de usá-lo como um
Maybe<U>
, desdeFalsy<T>
eFalsy<U>
têm os mesmos comportamentos - Você não deveria poder usar propriedades de
U
, porque o valor ainda pode ser falso. - Se você usá-lo em um
if
teste, então ele deve se tornar umTruthy<U>
no bloco daqueleif
declaração
Em outras palavras, Falsy<T> XOR Maybe<U>
é Maybe<U>
.Segue todas as mesmas regras.Você não precisa complicar o sistema de tipos aqui adicionando este estranho XOR
tipo, porque já existe um tipo que atende a todas as especificações que você precisa.
É como dar uma caixa a alguém e dizer “Esta é uma caixa vazia de lixo ou uma caixa cheia de recicláveis”.Você pode esvaziar com segurança o conteúdo da caixa na lixeira.