Por que a maioria dos idiomas tradicionais não suporta a sintaxe “x <y <z” para comparações booleanas em três direções?


34

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 < 2avalia como true, porque é realmente (1 < 3) < 2 === true < 2 === 1 < 2.

Então, minha pergunta é a seguinte: por que x < y < zgeralmente não está disponível em linguagens de programação, com a semântica esperada?


11
Aqui está o arquivo de gramática, que eles convenientemente ficar certo na documentação do Python - Eu não acho que é tão difícil: docs.python.org/reference/grammar.html
Aaron Hall

Não conheço outras linguagens tão bem quanto Python, mas posso falar da simplicidade da interpretação do Python. Talvez eu deva responder. Mas eu discordo da conclusão de gnasher729 sobre o dano causado.
Aaron Hall

@ErikEidt - A demanda está sendo capaz de escrever expressões matemáticas da maneira como fomos ensinados no ensino médio (ou anterior). Todo mundo que é matematicamente inclinado sabe o que $ a <b <c <d $ significa. Só porque um recurso existe, não significa que você precise usá-lo. Quem não gosta pode sempre fazer uma regra pessoal ou de projeto que proíba seu uso.
David Hammen

2
Acho que o que se resume é que é melhor para a equipe de C # (como um exemplo) explorar o LINQ e, no futuro, talvez tipos de registros e correspondência de padrões do que adicionar um pouco de açúcar sintático que salvaria as pessoas 4 pressionamentos de tecla e não realmente adicionar qualquer expressividade (você pode helpermethods também escrever como static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>se ele realmente incomoda de ver &&s)
sara

11
SQL é bastante "mainstream" e você pode escrever "x entre 1 e 10"
JoelFan

Respostas:


30

Estes são operadores binários que, quando encadeados, produzem normalmente e naturalmente uma árvore de sintaxe abstrata como:

árvore de sintaxe abstrata normal para operadores binários

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 fazer boolean < z. Para x < y < zfuncionar como discutido, você deve criar um caso especial no compilador para produzir uma árvore de sintaxe como:

árvore de sintaxe de caso especial

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.


11
"então você recebe um erro de tipo tentando fazer booleano <z" - não se o compilador permitir encadeamento avaliando y no local para a comparação z. "Isso adiciona muito espaço para as coisas darem errado que os designers de linguagem preferem evitar, se possível." Na verdade, o Python não tem nenhum problema em fazer isso, e a lógica da análise é limitada a uma única função: hg.python.org/cpython/file/tip/Python/ast.c#l1122 - não há muito espaço para as coisas acontecerem errado. "às vezes age como um operador binário e às vezes age efetivamente como um operador trinário". No Python, toda a cadeia de comparação é ternária.
Aaron Hall

2
Eu nunca disse que não era possível, apenas trabalho extra com complexidade extra. Outros idiomas não precisam escrever nenhum código separado apenas para manipular seus operadores de comparação. Você o obtém gratuitamente com outros operadores binários. Você apenas precisa especificar sua precedência.
Karl Bielefeldt

Sim, mas ... lá é já um operador ternário disponível em muitas línguas?
JensG

11
@JensG A denotação de ternário significa que são necessários 3 argumentos. No contexto do seu link, é um operador de condição ternário. Aparentemente, "trinário" em um termo cunhado para um operador que parece levar 2, mas na verdade leva 3. Meu principal problema com esta resposta é que é principalmente FUD.
Aaron Hall

2
Eu sou um dos que rejeitam esta resposta aceita. (@JesseTG: inaceite esta resposta.) Esta pergunta confunde o que x<y<zsignifica, ou mais importante x<y<=z. Esta resposta interpreta x<y<zcomo 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.
David Hammen

37

Por que x < y < zgeralmente não está disponível em linguagens de programação?

Nesta resposta concluo que

  • embora essa construção seja trivial de implementar na gramática de um idioma e crie valor para os usuários,
  • As principais razões pelas quais isso não existe na maioria dos idiomas devem-se à sua importância em relação a outros recursos e à falta de vontade dos órgãos de governo dos idiomas em
    • chatear os usuários com possíveis alterações
    • mover para implementar o recurso (ou seja: preguiça).

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:

comparison    ::=  or_expr ( comp_operator or_expr )*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

e a documentação declara ainda:

As comparações podem ser encadeadas arbitrariamente, por exemplo, x <y <= z é equivalente a x <y y = = z, exceto que y é avaliado apenas uma vez (mas em ambos os casos z não é avaliado quando x <y é encontrado ser falso).

Equivalência Lógica

tão

result = (x < y <= z)

é logicamente equivalente em termos de avaliação de x, y, e z, com a excepção de que yé avaliado duas vezes:

x_lessthan_y = (x < y)
if x_lessthan_y:       # z is evaluated contingent on x < y being True
    y_lessthan_z = (y <= z)
    result = y_lessthan_z
else:
    result = x_lessthan_y

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:

>>> import ast
>>> node_obj = ast.parse('"foo" < "bar" <= "baz"')
>>> ast.dump(node_obj)
"Module(body=[Expr(value=Compare(left=Str(s='foo'), ops=[Lt(), LtE()],
 comparators=[Str(s='bar'), Str(s='baz')]))])"

Portanto, podemos ver que isso realmente não é difícil para o Python ou qualquer outra linguagem analisar.

>>> ast.dump(node_obj, annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE()], [Str('bar'), Str('baz')]))])"
>>> ast.dump(ast.parse("'foo' < 'bar' <= 'baz' >= 'quux'"), annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE(), GtE()], [Str('bar'), Str('baz'), Str('quux')]))])"

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 < zsem 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.


Honestamente, eu tomo nenhum problema com a semântica fornecidos por linguagens que as operações de comparação de cadeia, tais como `x <y <z` mas é trivial para um desenvolvedor para mapear mentalmente x < y < zpara (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 < yproduz uma resposta binária {0}. y < zproduz uma resposta binária {1}. {0} && {1}produz a resposta descritiva. A lógica é composta, não ingenuamente encadeada.
K. Alan Bates

Para me comunicar melhor, prefaciei a resposta com uma única frase que resume diretamente todo o conteúdo. É uma frase longa, então eu a dividi em balas.
Aaron Hall

2
A principal razão pela qual poucas línguas implementam esse recurso é que, antes de Guido, ninguém sequer pensava nisso. As linguagens que herdam do C não podem fazer isso "certo" (matematicamente certo) agora, principalmente porque os desenvolvedores do C o fizeram "errado" (matematicamente errado) há mais de 40 anos. Há muito código por aí que depende da natureza contra-intuitiva de como essas linguagens interpretam x<y<z. Um idioma já teve uma chance de acertar algo assim, e essa chance está no início do idioma.
David Hammen

11
@ K.AlanBates Você faz 2 pontos: 1) o encadeamento do operador é desleixado e 2) o açúcar sintático não tem valor. Para o primeiro: demonstrei que o encadeamento de operadores é 100% determinístico, não demonstrei? Talvez alguns programadores sejam mentalmente preguiçosos demais para expandir sua capacidade de compreender a construção? Para o segundo ponto: Parece-me que você está argumentando diretamente contra a legibilidade? O açúcar sintático não é geralmente considerado bom quando melhora a legibilidade? Se é normal pensar dessa maneira, por que um programador não gostaria de se comunicar dessa maneira? O código deve ser escrito para ser lido, não?
Aaron Hall

2
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/...
user2023861

13

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.


4
Portanto, resumindo, é apenas um recurso difícil de projetar bem.
Jon Purdy

2
Este não é realmente um motivo para explicar por que nenhuma linguagem bem conhecida contém esse recurso. De fato, é muito fácil incluí-lo em um idioma de uma maneira bem definida. É apenas uma questão de visualizá-lo como uma lista conectada por operadores de tipo semelhante, em vez de todos os operadores serem binários. O mesmo pode ser feito para somas 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.
cmaster

11
Eu acho que no Python é um pouco de otimização ( x < y < zsendo equivalente a, ((x < y) and (y < z))mas yapenas 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 ...
Aaron Hall

Se o objetivo de alguém é projetar uma linguagem que elimine todas as coisas que possam ser confusas para os programadores mais tolos, essa linguagem já existe: COBOL. Eu prefiro usar python, eu mesmo, onde se pode realmente escrever 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, escrever 0 < x < 8em python tem o significado óbvio (sem aspas assustadoras) de que x fica entre 0 e 8, exclusivo.
precisa saber é o seguinte

@DavidHammen, ironicamente, COBOL, de fato, permitir que a <b <c
JoelFan

10

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ões true < ze false < znã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 < yem 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 < zcenário é muito mais complicado. Agora o compilador, com efeito, tem de moda três funções: x < y, y < zeo 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.


2
Se você estiver projetando o idioma, terá a oportunidade de torná-lo o resultado certo .
precisa saber é

2
Claro que responde à pergunta. Se a pergunta é realmente o porquê , a resposta é "porque foi isso que os designers de linguagem escolheram". Se você conseguir uma resposta melhor do que isso, vá em frente. Observe que Gnasher disse essencialmente exatamente a mesma coisa no primeiro parágrafo de sua resposta .
Robert Harvey

3
Mais uma vez, você está cortando cabelos. Programadores tendem a fazer isso. "Você quer tirar o lixo?" "Não." "Você vai tirar o lixo?" "Sim."
31716 Robert Harvey

2
Eu também contesto o último parágrafo. O Python suporta comparações de cadeias e seu analisador é LL (1). Também não é necessariamente difícil definir e implementar a semântica: o Python apenas diz que isso 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 como a == b is Truejá não têm o efeito pretendido.)

2
@RobertHarvey re:answerFoi aqui que minha mente também foi imediatamente para o meu comentário sobre a questão principal. Não considero suporte para x < y < zadicionar 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.
27316 Alan K. Bates

6

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

a < b < c

Poderíamos avaliá-lo estritamente da esquerda para a direita (associativo da esquerda):

a.__lt__(b).__lt__(c)

Mas agora chamamos __lt__o resultado de a.__lt__(b), que é a Boolean. Isso não faz sentido.

Vamos tentar associativamente:

a.__lt__(b.__lt__(c))

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__para a, passando be ccomo argumentos:

a.__lt__(b, c)

Ok, isso funciona, mas há uma estranha assimetria aqui: adecide se é menor que b. Mas bnão decide se é menor do que c, ao contrário, essa decisão também é tomada por a.

Que tal interpretá-lo como uma mensagem n-ária enviada para this?

this.__lt__(a, b, c)

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 que bnão é nem uma propriedade de anem de b), mas sim uma propriedade do contexto (ie this).

Do ponto de vista convencional, isso parece estranho. No entanto, por exemplo, em Haskell, isso é normal. Pode haver várias implementações diferentes da Ordclasse de tipo, por exemplo, e se é ou não amenor b, 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,

a = b

não é interpretado como enviando a mensagem =para apassar bcomo argumento, mas como enviando a mensagem =para a "Base atual" (um conceito semelhante, mas não idêntico a this) passando ae bcomo argumentos. Então, a = bé interpretado como

=(a, b)

e não

a =(b)

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 < cqual 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:

(<  a b c d) ; the sequence a, b, c, d is monotonically increasing
(>  a b c d) ; the sequence a, b, c, d is monotonically decreasing
(<= a b c d) ; the sequence a, b, c, d is monotonically non-decreasing
(>= a b c d) ; the sequence a, b, c, d is monotonically non-increasing

3

É 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).


11
Isso ainda será analisado com uma gramática normal em praticamente qualquer idioma convencional; simplesmente não será entendido corretamente pela razão que o @RobertHarvey deu.
Mason Wheeler

@MasonWheeler Não, não necessariamente. Se as regras forem escritas para que as comparações sejam intercambiáveis ​​com outros operadores (por exemplo, porque elas têm a mesma precedência), você não obterá o comportamento correto. O fato de o Python estar colocando todas as comparações em um nível é o que lhe permite tratar a sequência como uma conjunção.
Neil G

11
Na verdade não. Coloque 1 < 2 < 3em 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.
Mason Wheeler

2
@MasonWheeler O que estou dizendo é que a linguagem precisa ser projetada para funcionar. Uma parte disso é entender a gramática corretamente. (Se as regras forem escritas para que as comparações sejam intercambiáveis ​​com outros operadores, por exemplo, porque elas têm a mesma precedência, você não terá o comportamento correto.) Outra parte disso é a interpretação do AST como uma conjunção, que C ++ não faz. O ponto principal da minha resposta é que é uma decisão do designer de linguagem.
Neil G

@MasonWheeler Acho que concordamos. Eu estava apenas destacando que não é difícil entender a gramática corretamente. É apenas uma questão de decidir com antecedência que você deseja que funcione dessa maneira.
Neil G

2

O programa C ++ a seguir é compilado com nary peep from clang, mesmo com avisos definidos no nível mais alto possível ( -Weverything):

#include <iostream>
int main () { std::cout << (1 < 3 < 2) << '\n'; }

O conjunto de compiladores gnu, por outro lado, me adverte muito bem comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses].

Então, minha pergunta é a seguinte: por que x <y <z normalmente não está disponível em linguagens de programação, com a semântica esperada?

A resposta é simples: compatibilidade com versões anteriores. Existe uma grande quantidade de código na natureza que usa o equivalente 1<3<2e 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.


+1 porque este programa gera '1' como resultado de uma expressão que é obviamente falsa em matemática. Embora seja artificial, um exemplo do mundo real com nomes de variáveis ​​incompreensíveis se tornaria um pesadelo de depuração se esse recurso de idioma fosse adicionado.
Seth Battin

@ SethBattin - Este não é um pesadelo de depuração em Python. O único problema no Python é 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.
David Hammen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.