Se eu quiser comparar dois números (ou outras entidades bem ordenadas), eu o faria com x < y
. Se eu quiser comparar três deles, o aluno de álgebra do ensino médio sugerirá tentar x < y < z
. O programador em mim responderá com "não, isso não é válido, você precisa fazer x < y && y < z
".
A maioria das linguagens que encontrei não parece suportar essa sintaxe, o que é estranho, considerando o quão comum é em matemática. Python é uma exceção notável. O JavaScript parece uma exceção, mas é realmente apenas um subproduto infeliz da precedência do operador e conversões implícitas; no node.js, 1 < 3 < 2
avalia como true
, porque é realmente (1 < 3) < 2 === true < 2 === 1 < 2
.
Então, minha pergunta é a seguinte: por que x < y < z
geralmente não está disponível em linguagens de programação, com a semântica esperada?
static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>
se ele realmente incomoda de ver&&
s)Respostas:
Estes são operadores binários que, quando encadeados, produzem normalmente e naturalmente uma árvore de sintaxe abstrata como:
Quando avaliado (o que você faz das folhas para cima), isso produz um resultado booleano e
x < y
, em seguida, você recebe um erro de tipo tentando fazerboolean < z
. Parax < y < z
funcionar como discutido, você deve criar um caso especial no compilador para produzir uma árvore de sintaxe como:Não que não seja possível fazer isso. Obviamente, é, mas adiciona alguma complexidade ao analisador para um caso que não é exibido com tanta frequência. Você está basicamente criando um símbolo que às vezes age como um operador binário e, às vezes, age efetivamente como um operador ternário, com todas as implicações do tratamento de erros e tudo o que isso implica. Isso adiciona muito espaço para as coisas darem errado que os designers de linguagem preferem evitar, se possível.
fonte
x<y<z
significa, ou mais importantex<y<=z
. Esta resposta interpretax<y<z
como um operador trinário. É exatamente assim que essa expressão matemática bem definida não deve ser interpretada.x<y<z
é uma abreviação de(x<y)&&(y<z)
. As comparações individuais ainda são binárias.Por que
x < y < z
geralmente não está disponível em linguagens de programação?Nesta resposta concluo que
Introdução
Eu posso falar da perspectiva de um pythonista sobre esta questão. Sou usuário de um idioma com esse recurso e gosto de estudar os detalhes de implementação do idioma. Além disso, eu estou um pouco familiarizado com o processo de alterar linguagens como C e C ++ (o padrão ISO é governado pelo comitê e versionado por ano).
Documentação e implementação do Python
A partir dos documentos / gramática, vemos que podemos encadear qualquer número de expressões com operadores de comparação:
e a documentação declara ainda:
Equivalência Lógica
tão
é logicamente equivalente em termos de avaliação de
x
,y
, ez
, com a excepção de quey
é avaliado duas vezes:Novamente, a diferença é que y é avaliado apenas uma vez
(x < y <= z)
.(Observe, os parênteses são completamente desnecessários e redundantes, mas eu os usei para o benefício de outros idiomas, e o código acima é bastante legal em Python.)
Inspecionando a árvore de sintaxe abstrata analisada
Podemos inspecionar como o Python analisa operadores de comparação encadeados:
Portanto, podemos ver que isso realmente não é difícil para o Python ou qualquer outra linguagem analisar.
E, ao contrário da resposta atualmente aceita, a operação ternária é uma operação de comparação genérica, que pega a primeira expressão, uma iterável de comparações específicas e uma iterável de nós de expressão para avaliar conforme necessário. Simples.
Conclusão sobre Python
Pessoalmente, acho que a semântica de alcance é bastante elegante, e a maioria dos profissionais de Python que eu conheço encorajaria o uso do recurso, em vez de considerá-lo prejudicial - a semântica é claramente declarada na documentação de boa reputação (como observado acima).
Observe que o código é lido muito mais do que está escrito. As mudanças que melhoram a legibilidade do código devem ser adotadas, sem descontar o aumento de espectros genéricos de Medo, Incerteza e Dúvida .
Então, por que x <y <z normalmente não está disponível nas linguagens de programação?
Eu acho que há uma confluência de razões que se concentram na importância relativa do recurso e no momento / inércia de mudança permitidos pelos governadores das línguas.
Perguntas semelhantes podem ser feitas sobre outros recursos de idioma mais importantes
Por que a herança múltipla não está disponível em Java ou C #? Não há uma boa resposta aqui para qualquer uma das perguntas . Talvez os desenvolvedores tenham sido preguiçosos, como Bob Martin alega, e as razões apresentadas são apenas desculpas. E herança múltipla é um tópico bastante grande na ciência da computação. É certamente mais importante que o encadeamento do operador.
Soluções alternativas simples
O encadeamento do operador de comparação é elegante, mas não é tão importante quanto a herança múltipla. E assim como Java e C # têm interfaces como solução alternativa, todas as linguagens para várias comparações - você simplesmente encadeia as comparações com "e" s booleanos, o que funciona com bastante facilidade.
A maioria dos idiomas é governada por comitê
A maioria das línguas está evoluindo por comitê (em vez de ter um ditador benevolente sensato para a vida como o Python). E especulo que esse assunto não tenha recebido apoio suficiente para sair de seus respectivos comitês.
Os idiomas que não oferecem esse recurso podem mudar?
Se uma linguagem permitir
x < y < z
sem a semântica matemática esperada, isso seria uma mudança radical. Se não o permitisse, seria quase trivial adicionar.Quebrando mudanças
Em relação aos idiomas com alterações de quebra: atualizamos os idiomas com mudanças de comportamento - mas os usuários tendem a não gostar disso, principalmente os usuários de recursos que podem estar com problemas. Se um usuário confiar no comportamento anterior de
x < y < z
, provavelmente protestaria em voz alta. E como a maioria das línguas é governada por um comitê, duvido que tenhamos muita vontade política para apoiar essa mudança.fonte
x < y < z
para(x < y) && (y < z)
. Nits de colheita, o modelo mental para a comparação encadeada é matemática geral. A comparação clássica não é matemática em geral, mas lógica booleana.x < y
produz uma resposta binária{0}
.y < z
produz uma resposta binária{1}
.{0} && {1}
produz a resposta descritiva. A lógica é composta, não ingenuamente encadeada.x<y<z
. Um idioma já teve uma chance de acertar algo assim, e essa chance está no início do idioma.I have watched both Ruby and Python implement breaking changes.
Para aqueles que estão curiosos, aqui está uma alteração significativa no C # 5.0 envolvendo variáveis de laço e fechamentos: blogs.msdn.microsoft.com/ericlippert/2009/11/12/...As linguagens de computador tentam definir as menores unidades possíveis e permitem combiná-las. A menor unidade possível seria algo como "x <y", que fornece um resultado booleano.
Você pode solicitar um operador ternário. Um exemplo seria x <y <z. Agora, quais combinações de operadores permitimos? Obviamente x> y> z ou x> = y> = z ou x> y> = z ou talvez x == y == z deve ser permitido. E quanto a x <y> z? x! = y! = z? O que o último significa, x! = Ye y = z ou que todos os três são diferentes?
Agora promoções de argumento: em C ou C ++, os argumentos seriam promovidos para um tipo comum. Então, o que x <y <z significa x é o dobro, mas y e z são long int longo? Todos os três promovidos para dobrar? Ou y é considerado o dobro uma vez e o tempo inteiro na outra vez? O que acontece se em C ++ um ou ambos os operadores estiverem sobrecarregados?
E por último, você permite algum número de operandos? Como a <b> c <d> e <f> g?
Bem, tudo fica muito complicado. Agora, o que eu não me importaria é que x <y <z produza um erro de sintaxe. Porque a utilidade disso é pequena em comparação com o dano causado aos iniciantes que não conseguem descobrir o que x <y <z realmente faz.
fonte
x + y + z
, com a única diferença que isso não implica em nenhuma diferença semântica. Portanto, é que nenhuma linguagem conhecida jamais se importou em fazê-lo.x < y < z
sendo equivalente a,((x < y) and (y < z))
masy
apenas avaliada uma vez ), que eu imagino que as linguagens compiladas otimizem o caminho. "Porque a utilidade disso é pequena em comparação com o dano causado aos iniciantes que não conseguem descobrir o que x <y <z realmente faz." Eu acho que é incrivelmente útil. Provavelmente vai -1 para que ...a < b > c < d > e < f > g
, com o significado "óbvio"(a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g)
. Só porque você pode escrever isso não significa que você deveria. Eliminar essas monstruosidades é o objetivo da revisão de código. Por outro lado, escrever0 < x < 8
em python tem o significado óbvio (sem aspas assustadoras) de que x fica entre 0 e 8, exclusivo.Em muitas linguagens de programação,
x < y
é uma expressão binária que aceita dois operandos e avalia como um único resultado booleano. Portanto, se encadear várias expressõestrue < z
efalse < z
não fizer sentido, e se essas expressões forem avaliadas com êxito, é provável que produzam o resultado errado.É muito mais fácil pensar
x < y
em uma chamada de função que usa dois parâmetros e produz um único resultado booleano. De fato, é quantas línguas o implementam sob o capô. É comporável, facilmente compilável e simplesmente funciona.O
x < y < z
cenário é muito mais complicado. Agora o compilador, com efeito, tem de moda três funções:x < y
,y < z
eo resultado desses dois valores anded juntos, tudo dentro do contexto de um indiscutivelmente gramática linguagem ambígua .Por que eles fizeram o contrário? Porque é uma gramática inequívoca, muito mais fácil de implementar e muito mais fácil de corrigir.
fonte
e1 op1 e2 op2 e3 op3 ...
é equivalente,e1 op e2 and e2 op2 e3 and ...
exceto que cada expressão é avaliada apenas uma vez. (BTW esta regra simples tem o efeito colateral confundindo que declarações comoa == b is True
já não têm o efeito pretendido.)re:answer
Foi aqui que minha mente também foi imediatamente para o meu comentário sobre a questão principal. Não considero suporte parax < y < z
adicionar qualquer valor específico à semântica da linguagem.(x < y) && (y < z)
é mais amplamente suportado, é mais explícito, mais expressivo, mais facilmente digerido em seus constituintes, mais compostável, mais lógico, mais facilmente refatorado.A maioria das linguagens convencionais é (pelo menos parcialmente) orientada a objetos. Fundamentalmente, o princípio subjacente do OO é que os objetos enviam mensagens para outros objetos (ou eles mesmos), e o destinatário dessa mensagem tem controle total sobre como responder a essa mensagem.
Agora, vamos ver como implementaríamos algo como
Poderíamos avaliá-lo estritamente da esquerda para a direita (associativo da esquerda):
Mas agora chamamos
__lt__
o resultado dea.__lt__(b)
, que é aBoolean
. Isso não faz sentido.Vamos tentar associativamente:
Nah, isso também não faz sentido. Agora nós temos
a < (something that's a Boolean)
.Ok, que tal tratá-lo como açúcar sintático. Vamos fazer uma cadeia de n
<
comparações enviar uma mensagem n-1-ária. Isso pode significar que enviamos a mensagem__lt__
paraa
, passandob
ec
como argumentos:Ok, isso funciona, mas há uma estranha assimetria aqui:
a
decide se é menor queb
. Masb
não decide se é menor do quec
, ao contrário, essa decisão também é tomada pora
.Que tal interpretá-lo como uma mensagem n-ária enviada para
this
?Finalmente! Isso pode funcionar. Isso significa, no entanto, que a ordenação de objetos não é mais uma propriedade do objeto (por exemplo, se
a
é menor do queb
não é nem uma propriedade dea
nem deb
), mas sim uma propriedade do contexto (iethis
).Do ponto de vista convencional, isso parece estranho. No entanto, por exemplo, em Haskell, isso é normal. Pode haver várias implementações diferentes da
Ord
classe de tipo, por exemplo, e se é ou nãoa
menorb
, depende de qual instância da classe de tipo está no escopo.Mas, na verdade, não é que estranho em tudo! Java (
Comparator
) e .NET (IComparer
) têm interfaces que permitem injetar sua própria relação de pedidos em, por exemplo, algoritmos de classificação. Assim, eles reconhecem plenamente que uma ordem não é algo que é fixo a um tipo, mas depende do contexto.Até onde eu sei, atualmente não há idiomas que realizem essa tradução. Há uma precedência, no entanto: tanto a Ioke quanto a Seph têm o que seu designer chama de "operadores trinários" - operadores que são sintaticamente binários, mas semanticamente ternários. Em particular,
não é interpretado como enviando a mensagem
=
paraa
passarb
como argumento, mas como enviando a mensagem=
para a "Base atual" (um conceito semelhante, mas não idêntico athis
) passandoa
eb
como argumentos. Então,a = b
é interpretado comoe não
Isso poderia ser facilmente generalizado para operadores n-ários.
Observe que isso é realmente peculiar aos idiomas OO. No OO, sempre temos um único objeto que é responsável por interpretar o envio de uma mensagem e, como vimos, não é imediatamente óbvio para algo como
a < b < c
qual objeto deve ser.Isso não se aplica a linguagens procedurais ou funcionais. Por exemplo, em Scheme , Common Lisp e Clojure , a
<
função é n-ária e pode ser chamada com um número arbitrário de argumentos.Em particular,
<
faz não significa "menos que", em vez estas funções são interpretados de forma ligeiramente diferente:fonte
É simplesmente porque os designers da linguagem não pensaram nisso ou não acharam uma boa ideia. O Python faz isso como você descreveu com uma gramática simples (quase) LL (1).
fonte
1 < 2 < 3
em Java ou C # e você não terá problemas com a precedência do operador; você tem um problema com tipos inválidos. O problema é que isso ainda será analisado exatamente como você o escreveu, mas você precisa de uma lógica de caso especial no compilador para transformá-lo de uma sequência de comparações individuais para uma comparação encadeada.O programa C ++ a seguir é compilado com nary peep from clang, mesmo com avisos definidos no nível mais alto possível (
-Weverything
):O conjunto de compiladores gnu, por outro lado, me adverte muito bem
comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
.A resposta é simples: compatibilidade com versões anteriores. Existe uma grande quantidade de código na natureza que usa o equivalente
1<3<2
e espera que o resultado seja verdadeiro.Um designer de linguagem tem apenas uma chance de fazer isso "certo", e esse é o momento em que a linguagem é projetada pela primeira vez. Entendê-lo "errado" inicialmente significa que outros programadores tirarão vantagem rapidamente desse comportamento "errado". Colocá-lo "certo" na segunda vez quebrará essa base de código existente.
fonte
if x == y is True : ...
, na minha opinião: as pessoas que escrevem esse tipo de código merecem ser submetidas a algum tipo de tortura extra-especial e extraordinária que (se ele estivesse vivo agora) faria o próprio Torquemada desmaiar.