Estou desenvolvendo uma linguagem que pretendo substituir Javascript e PHP. (Não vejo nenhum problema com isso. Não é como se um desses idiomas tivesse uma grande base de instalação.)
Uma das coisas que eu queria mudar era transformar o operador de atribuição em um comando de atribuição, removendo a capacidade de fazer uso do valor retornado.
x=1; /* Assignment. */
if (x==1) {} /* Comparison. */
x==1; /* Error or warning, I've not decided yet. */
if (x=1) {} /* Error. */
Eu sei que isso significaria que aquelas funções de linha única que as pessoas C amam tanto não funcionariam mais. Imaginei (com poucas evidências além da minha experiência pessoal) que a grande maioria das vezes que isso acontecia, era realmente uma operação de comparação.
Ou é? Existem usos práticos do valor de retorno do operador de atribuição que não puderam ser reescritos trivialmente? (Para qualquer idioma que tenha esse conceito.)
fonte
while((x = getValue()) != null) {}
. As substituições serão mais feias, pois você precisará usarbreak
ou repetir ax = getValue
tarefa.Respostas:
Tecnicamente, vale a pena manter um pouco de açúcar sintático, mesmo que possa ser substituído trivialmente, se melhorar a legibilidade de alguma operação comum. Mas a atribuição como expressão não se enquadra nisso. O perigo de digitá-lo no lugar de uma comparação significa que raramente é usado (às vezes até proibido por guias de estilo) e provoca uma visão dupla sempre que é usado. Em outras palavras, os benefícios de legibilidade são pequenos em número e magnitude.
Uma olhada nos idiomas existentes que fazem isso pode valer a pena.
if (x)
no lugarif (x != null)
ouif (x != 0)
dependendo do tipo dex
.No entanto, o Python permite que um caso especial, atribuindo a vários nomes de uma só vez:
a = b = c
. Esta é considerada uma declaração equivalente ab = c; a = b
, e é ocasionalmente usada, por isso pode valer a pena adicionar ao seu idioma também (mas eu não me preocuparia, pois essa adição deve ser compatível com versões anteriores).fonte
a = b = c
que as outras respostas não trazem realmente.:=
para atribuição.=
é atribuição,==
é comparação.if (a = true)
lançará um aviso C4706 (The test value in a conditional expression was the result of an assignment.
). O GCC com C também lançará awarning: suggest parentheses around assignment used as truth value [-Wparentheses]
. Esses avisos podem ser silenciados com um conjunto extra de parênteses, mas eles existem para incentivar explicitamente a indicação de que a atribuição foi intencional.a = true
se avaliar a um valor booleano e, portanto, não é um erro, mas também levanta uma advertência relacionada em C #.De um modo geral, não. A idéia de ter o valor de uma expressão de atribuição ser o valor atribuído significa que temos uma expressão que pode ser usada tanto para seu efeito colateral quanto para seu valor , e que é considerada por muitos confusa.
Usos comuns costumam tornar as expressões compactas:
possui a semântica em C # de "converta z para o tipo de y, atribua o valor convertido para y, o valor convertido é o valor da expressão, converta isso para o tipo de x, atribua para x".
Mas já estamos no campo dos efeitos colaterais impertinentes em um contexto de declaração, então há realmente muito pouco benefício convincente nisso
Da mesma forma com
sendo uma abreviação de
Novamente, no código original, estamos usando uma expressão para seus efeitos colaterais e seu valor, e estamos fazendo uma declaração que tem dois efeitos colaterais em vez de um. Ambos são fedorentos; tente ter um efeito colateral por instrução e use expressões para seus valores, não para seus efeitos colaterais.
Se você realmente deseja ser ousado e enfatizar que a atribuição é uma declaração e não uma igualdade, meu conselho é: torne claramente uma declaração de atribuição .
O vermelho. Ou
ou melhor ainda:
Ou melhor ainda
Não há absolutamente nenhuma maneira com que alguém seja confundido
x == 1
.fonte
x[⍋x←6?40]
APL exigia seu próprio teclado especial, mas era uma linguagem bem-sucedida.a+b*c --> x
? Isso me parece estranho.Muitas linguagens escolhem o caminho de tornar a atribuição uma declaração, e não uma expressão, incluindo Python:
e Golang:
Outros idiomas não têm atribuição, mas ligações de escopo, por exemplo, OCaml:
No entanto,
let
é uma expressão em si.A vantagem de permitir a atribuição é que podemos verificar diretamente o valor de retorno de uma função dentro do condicional, por exemplo, neste snippet Perl:
O Perl também escopo a declaração apenas para essa condicional, o que a torna muito útil. Ele também avisará se você atribuir dentro de uma condicional sem declarar uma nova variável lá -
if ($foo = $bar)
avisará,if (my $foo = $bar)
não avisará .Fazer a atribuição em outra declaração geralmente é suficiente, mas pode trazer problemas de escopo:
Golang depende muito dos valores de retorno para verificação de erros. Portanto, permite que um condicional leve uma instrução de inicialização:
Outros idiomas usam um sistema de tipos para proibir expressões não-booleanas dentro de um condicional:
É claro que isso falha ao usar uma função que retorna um booleano.
Vimos agora diferentes mecanismos de defesa contra atribuição acidental:
let
ligaçõesClassifiquei-os em ordem crescente de preferência - atribuições dentro de expressões podem ser úteis (e é simples contornar os problemas do Python tendo uma sintaxe de declaração explícita e uma sintaxe de argumento nomeada diferente). Mas não há problema em impedi-los, pois há muitas outras opções para o mesmo efeito.
O código sem erros é mais importante que o código conciso.
fonte
Você disse: "Imaginei (com poucas evidências além da minha experiência pessoal) que a grande maioria das vezes que isso acontecia, era realmente uma operação de comparação".
Por que não corrigir o problema?
Em vez de = para atribuição e == para teste de igualdade, por que não usar: = para atribuição e = (ou mesmo ==) para igualdade?
Observar:
Se você quiser tornar mais difícil para o programador confundir atribuição com igualdade, torne mais difícil.
Ao mesmo tempo, se você REALMENTE quisesse corrigir o problema, removeria o crock C que afirmava que os booleanos eram apenas números inteiros com nomes simbólicos predefinidos de açúcar. Faça deles um tipo completamente diferente. Então, ao invés de dizer
você força o programador a escrever:
O fato é que a atribuição como operador é uma construção muito útil. Não eliminamos as lâminas de barbear porque algumas pessoas se cortam. Em vez disso, o rei Gillette inventou o aparelho de barbear.
fonte
:=
para atribuição e=
igualdade pode resolver esse problema, mas à custa de alienar todos os programadores que não cresceram usando um pequeno conjunto de linguagens não convencionais. (2) Outros tipos de bools permitidos em condições nem sempre são devidos à mistura de bools e números inteiros; é suficiente fornecer uma interpretação verdadeira / falsa para outros tipos. Linguagens mais recentes que não têm medo de se desviar de C fizeram isso com muitos tipos diferentes de números inteiros (por exemplo, o Python considera as coleções vazias falsas).if (a = b)
lvalue a, boolean a, b). Em um idioma sem digitação estática, também fornece mensagens de erro muito melhores (em tempo de análise versus tempo de execução). Além disso, prevenir "a = b vs. a == b erros" pode não ser o único objetivo relevante. Por exemplo, eu também gostaria de permitir que o códigoif items:
significasseif len(items) != 0
, e que eu teria que desistir de restringir as condições aos booleanos.Para realmente responder à pergunta, sim, existem vários usos disso, embora sejam um pouco de nicho.
Por exemplo em Java:
A alternativa sem usar a atribuição incorporada requer o
ob
definido fora do escopo do loop e dois locais de código separados que chamam x.next ().Já foi mencionado que você pode atribuir várias variáveis em uma etapa.
Esse tipo de coisa é o uso mais comum, mas programadores criativos sempre têm mais.
fonte
ob
objeto com cada loop?Como você cria todas as regras, por que agora permitir que a atribuição gere um valor e simplesmente não permitir tarefas dentro de etapas condicionais? Isso fornece o açúcar sintático para facilitar as inicializações, enquanto ainda evita um erro comum de codificação.
Em outras palavras, torne isso legal:
Mas torne isso ilegal:
fonte
a = b = c
parece mais ortogonal e mais fácil de implementar também. Essas duas abordagens discordam da atribuição nas expressões (a + (b = c)
), mas você não tomou partido delas, portanto, presumo que elas não importam.Pelo que parece, você está no caminho de criar uma linguagem bastante rígida.
Com isso em mente, forçando as pessoas a escrever:
ao invés de:
pode parecer uma melhoria para impedir que as pessoas façam:
quando eles pretendiam fazer:
mas, no final, esse tipo de erro é fácil de detectar e avisa se eles são ou não códigos legais.
No entanto, existem situações em que:
não significa que
será verdade.
Se c for realmente uma função c (), poderá retornar resultados diferentes sempre que for chamado. (também pode ser computacionalmente caro também ...)
Da mesma forma, se c é um ponteiro para o hardware mapeado na memória,
é provável que ambos sejam diferentes e também possam ter efeitos eletrônicos no hardware em cada leitura.
Existem muitas outras permutações de hardware nas quais você precisa ser preciso sobre quais endereços de memória são lidos, gravados e com restrições de tempo específicas, nas quais executar várias atribuições na mesma linha é rápida, simples e óbvia, sem os riscos de tempo que variáveis temporárias introduzem
fonte
a = b = c
não éa = c; b = c
, éb = c; a = b
. Isso evita a duplicação de efeitos colaterais e também mantém a modificaçãoa
eb
na mesma ordem. Além disso, todos esses argumentos relacionados ao hardware são meio estúpidos: a maioria das linguagens não são linguagens de sistema e não são projetadas para resolver esses problemas, nem estão sendo usadas em situações em que esses problemas ocorrem. Isso vale duplamente para uma linguagem que tenta substituir o JavaScript e / ou PHP.a=b=c
é comum / útil, são exemplos de lugares onde a ordem e o número de efeitos colaterais devem ser tratados. Isso é totalmente independente. Reescreva a atribuição encadeada corretamente e as duas variantes estão igualmente corretas.b
em uma temperatura e que é convertido para o tipo dea
em outra temperatura. O tempo relativo de quando esses valores são realmente armazenados não é especificado. De uma perspectiva de design de linguagem, eu consideraria razoável exigir que todos os valores em uma declaração de atribuição múltipla tenham um tipo correspondente e possivelmente também exigir que nenhum deles seja volátil.O maior benefício para mim de ter uma tarefa como uma expressão é que ela permite que sua gramática seja mais simples se um de seus objetivos for "tudo é uma expressão" - um objetivo do LISP em particular.
Python não tem isso; possui expressões e declarações, sendo atribuição uma declaração. Mas como o Python define um
lambda
formulário como sendo uma única expressão parametrizada , isso significa que você não pode atribuir variáveis dentro de uma lambda. Às vezes, isso é inconveniente, mas não é um problema crítico, e é a única desvantagem na minha experiência de ter uma atribuição como uma declaração em Python.Uma maneira de permitir que a atribuição, ou melhor, o efeito da atribuição, seja uma expressão sem introduzir o potencial de
if(x=1)
acidentes que C tem é usar umalet
construção semelhante ao LISP , como a(let ((x 2) (y 3)) (+ x y))
que pode ser avaliada no seu idioma como5
. Usandolet
desta forma não precisa ser tecnicamente atribuição em tudo na sua língua, se você definirlet
como a criação de um escopo lexical. Definido dessa maneira, umalet
construção pode ser compilada da mesma maneira que a construção e a chamada de uma função de fechamento aninhado com argumentos.Por outro lado, se você está simplesmente preocupado com o
if(x=1)
caso, mas deseja que a atribuição seja uma expressão como em C, talvez apenas a escolha de tokens diferentes seja suficiente. Cessão:x := 1
oux <- 1
. Comparação:x == 1
. Erro de sintaxe:x = 1
.fonte
let
difere da atribuição de mais maneiras do que introduzir tecnicamente uma nova variável em um novo escopo. Para iniciantes, ele não tem efeito no código fora dolet
corpo do e, portanto, requer aninhar todo o código (o que deve usar a variável), uma desvantagem significativa no código pesado de atribuição. Se alguém seguisse esse caminho,set!
seria o melhor analógico Lisp - completamente diferente da comparação, mas não exigindo aninhamento ou um novo escopo.De fato. Isso não é novidade, todos os subconjuntos seguros da linguagem C já chegaram a essa conclusão.
MISRA-C, CERT-C e assim por diante, todas as atribuições de proibição dentro de condições, simplesmente por serem perigosas.
Não existe nenhum caso em que o código que depende de atribuição dentro de condições não possa ser reescrito.
Além disso, esses padrões também alertam contra a criação de código que depende da ordem da avaliação. Várias atribuições em uma única linha
x=y=z;
é o caso. Se uma linha com várias atribuições contiver efeitos colaterais (chamar funções, acessar variáveis voláteis etc.), não será possível saber qual efeito colateral ocorrerá primeiro.Não há pontos de sequência entre a avaliação dos operandos. Portanto, não podemos saber se a subexpressão
y
é avaliada antes ou depoisz
: é um comportamento não especificado em C. Portanto, esse código é potencialmente não confiável, não portátil e não conforme com os subconjuntos seguros mencionados de C.A solução teria sido substituir o código por
y=z; x=y;
. Isso adiciona um ponto de sequência e garante a ordem da avaliação.Portanto, com base em todos os problemas que isso causou em C, qualquer linguagem moderna faria bem em proibir a atribuição dentro de condições, bem como em várias atribuições em uma única linha.
fonte