É um pouco subjetivo, mas espero ter uma compreensão mais clara de quais fatores tornam um operador claro para usar vs obtuso e difícil. Eu estive pensando em designs de idiomas recentemente, e um problema em que sempre volto é quando fazer uma operação importante no idioma como operador e quando usar uma palavra-chave ou função.
Haskell é um pouco notório por isso, pois os operadores personalizados são fáceis de criar e, muitas vezes, um novo tipo de dados é fornecido com vários operadores para uso nele. A biblioteca Parsec, por exemplo, vem com uma tonelada de operadores para combinar analisadores, com gemas como >.
e .>
nem me lembro o que elas significam agora, mas lembro que elas eram muito fáceis de trabalhar depois de memorizar o que eles realmente querem dizer. Uma chamada de função como leftCompose(parser1, parser2)
teria sido melhor? Certamente mais detalhado, mas mais claro em alguns aspectos.
As sobrecargas de operadores em linguagens do tipo C são um problema semelhante, mas conflitadas pelo problema adicional de sobrecarregar o significado de operadores familiares, como +
com novos significados incomuns.
Em qualquer novo idioma, isso parece uma questão bastante difícil. No F #, por exemplo, a conversão usa um operador de conversão de tipo derivado matematicamente, em vez da sintaxe de conversão do estilo C # ou do estilo VB detalhado. C #: (int32) x
VB: CType(x, int32)
F #:x :> int32
Em teoria, uma nova linguagem poderia ter operadores para a maioria das funcionalidades internas. Em vez de def
ou dec
ou var
para declaração de variável, por que não ! name
ou @ name
ou algo semelhante. Certamente reduz a declaração seguida pela ligação: em @x := 5
vez de declare x = 5
ou let x = 5
A maioria dos códigos exigirá muitas definições de variáveis, então por que não?
Quando um operador é claro e útil e quando está obscurecendo?
fonte
+
para concatenação de strings ou<<
fluxos). Com Haskell, por outro lado, um operador é apenas uma função com um nome personalizado (ou seja, sem sobrecarga) ou parte de uma classe de tipo, o que significa que, embora seja polimórfico, faz a mesma coisa lógica para cada tipo e tem até o mesmo tipo de assinatura. O mesmo>>
vale>>
para todos os tipos e nunca será uma pequena mudança.Respostas:
do ponto de vista geral do design da linguagem, não há diferença entre funções e operadores. Pode-se descrever funções como operações de prefixo com qualquer entidade (ou mesmo variável). E as palavras-chave podem ser vistas apenas como funções ou operadores com nomes reservados (o que é útil na criação de uma gramática).
Todas essas decisões acabam se resumindo a como você deseja que a notação seja lida e que, como diz, é subjetiva, embora se possa fazer algumas racionalizações óbvias, por exemplo, use os operadores de infixo comuns para matemática, como todos os conhecem.
Por fim, Dijkstra escreveu uma justificativa interessante das notações matemáticas que ele usou, o que inclui uma boa discussão sobre as compensações das notações infix x prefixo
fonte
Para mim, um operador deixa de ser útil quando você não consegue mais ler a linha de código em voz alta ou em sua mente, e faz sentido.
Por exemplo,
declare x = 5
lê como"declare x equals 5"
oulet x = 5
pode ser lido como"let x equal 5"
, o que é muito compreensível quando lido em voz alta; no entanto, a leitura@x := 5
é lida como"at x colon equals 5"
(ou"at x is defined to be 5"
se você é um matemático), o que não faz sentido.Portanto, na minha opinião, use um operador se o código puder ser lido em voz alta e compreendido por uma pessoa razoavelmente inteligente que não esteja familiarizada com o idioma, mas use nomes de funções se não.
fonte
@x := 5
difícil descobrir - eu ainda leio em voz alta para mim mesmoset x to be equal to 5
.:=
tem um uso mais amplo em notação matemática fora das linguagens de programação. Além disso, declarações comox = x + 1
se tornam um pouco absurdas.:=
como tarefa. Alguns idiomas realmente usam isso. Smalltalk, eu acho, talvez seja o mais conhecido. Se a atribuição e a igualdade devem ser operadores separados é uma lata de worms totalmente diferente, uma provavelmente já coberta por outra questão.:=
era usado em matemática. A única definição que encontrei foi "está definida para ser", portanto@x := 5
seria traduzida em inglês para"at x is defined to be 5"
, o que para mim ainda não faz tanto sentido quanto deveria.Um operador é claro e útil quando é familiar. E isso provavelmente significa que sobrecarregar os operadores deve ser feito apenas quando o operador escolhido estiver próximo o suficiente (como forma, precedência e associatividade) como uma prática já estabelecida.
Mas cavando um pouco mais, há dois aspectos.
A sintaxe (infixo para operador em vez de prefixo para sintaxe de chamada de função) e a nomeação.
Para a sintaxe, há outro caso em que o infixo é mais claro que o prefixo, o que pode justificar o esforço de se tornar familiar: quando as chamadas são encadeadas e aninhadas.
Compare
a * b + c * d + e * f
com+(+(*(a, b), *(c, d)), *(e, f))
ou+ + * a b * c d * e f
(ambos são analisáveis na mesma condição que a versão infix). O fato de+
separar os termos, em vez de precedê-los, torna-os mais legíveis aos meus olhos (mas você precisa se lembrar de regras de precedência que não são necessárias para a sintaxe do prefixo). Se você precisar compor as coisas dessa maneira, o benefício a longo prazo valerá o custo de aprendizado. E você mantém essa vantagem mesmo que nomeie seus operadores com letras em vez de usar símbolos.Em relação à nomeação de operadores, vejo poucas vantagens em usar algo além de símbolos estabelecidos ou nome claro. Sim, eles podem ser mais curtos, mas são realmente enigmáticos e serão rapidamente esquecidos se você não tiver um bom motivo para conhecê-los.
Um terceiro aspecto, se você usa um POV de design de linguagem, é se o conjunto de operadores deve ser aberto ou fechado - você pode adicionar um operador ou não - e se a prioridade e a associatividade devem ser especificadas pelo usuário ou não. Minha primeira abordagem seria ter cuidado e não fornecer prioridade e associatividade especificáveis ao usuário, enquanto eu estaria mais aberto a ter um conjunto aberto (aumentaria o impulso de sobrecarregar o operador, mas diminuiria o uso de um mau) apenas para se beneficiar da sintaxe infix, onde é útil).
fonte
Operadores como símbolos são úteis quando intuitivamente fazem sentido.
+
e-
obviamente são adição e subtração, por exemplo, e é por isso que praticamente toda linguagem as usa como operadores. O mesmo acontece com a comparação (<
e>
são ensinadas na escola primária<=
e>=
são extensões intuitivas das comparações básicas quando você entende que não há teclas de comparação sublinhadas no teclado padrão.)*
e/
são menos óbvios imediatamente, mas são usados universalmente e sua posição no teclado numérico ao lado das teclas+
e-
ajuda a fornecer contexto. Cs<<
e>>
estão na mesma categoria: quando você entende o que é o desvio para a esquerda e o desvio para a direita, não é muito difícil entender as mnemônicas por trás das setas.Então você chega a alguns dos realmente estranhos, coisas que podem transformar o código C em uma sopa ilegível de operador. (Escolher C aqui porque é um exemplo muito pesado para o operador, cuja sintaxe praticamente todos estão familiarizados, através do trabalho com C ou com idiomas descendentes.) Por exemplo, então
%
operador. Todo mundo sabe o que é isso: é um sinal de porcentagem ... certo? Exceto em C, não tem nada a ver com divisão por 100; é o operador do módulo. Isso não faz sentido mnemonicamente, mas aí está!Pior ainda são os operadores booleanos.
&
como e faz sentido, exceto que existem dois&
operadores diferentes que fazem duas coisas diferentes e ambos são sintaticamente válidos em qualquer caso em que um deles seja válido, mesmo que apenas um deles realmente faça sentido em qualquer caso. Isso é apenas uma receita para confusão! Os operadores or , xor e not são ainda piores, pois não usam símbolos mnemonicamente úteis e também não são consistentes. (Nenhum operador de duplo xor e o lógico e o bit a bit não usam dois símbolos diferentes em vez de um símbolo e uma versão dobrada.)E, só para piorar, os operadores
&
e*
são reutilizados para coisas completamente diferentes, o que dificulta a análise por parte das pessoas e dos compiladores. (O queA * B
significa? Depende inteiramente do contexto: éA
um tipo ou uma variável?)É certo que nunca falei com Dennis Ritchie e perguntei sobre as decisões de design que ele tomou na linguagem, mas parece que a filosofia por trás dos operadores era "símbolos por causa de símbolos".
Compare isso com Pascal, que tem os mesmos operadores que C, mas uma filosofia diferente para representá-los: os operadores devem ser intuitivos e fáceis de ler. Os operadores aritméticos são os mesmos. O operador de módulo é a palavra
mod
, porque não há símbolo no teclado que obviamente signifique "módulo". Os operadores lógicos sãoand
,or
,xor
enot
, e há apenas um de cada; o compilador sabe se você precisa da versão booleana ou bit a bit, dependendo de estar operando em booleanos ou números, eliminando toda uma classe de erros. Isso torna o código Pascal muito mais fácil de entender do que o código C, especialmente em um editor moderno com destaque de sintaxe para que os operadores e as palavras-chave sejam visualmente diferentes dos identificadores.fonte
and
eor
o tempo todo. Quando ele desenvolveu Pascal, no entanto, ele fez isso em um mainframe de Dados de Controle, que usava caracteres de 6 bits. Isso forçou a decisão de usar palavras para muitas coisas, pela simples razão de que o conjunto de caracteres simplesmente não tinha tanto espaço para símbolos e algo como ASCII. Eu usei C e Pascal, e acho C substancialmente mais legível. Você pode preferir Pascal, mas isso é uma questão de gosto, não de fato.|
(e, por extensão||
) , a favor ou faz sentido. Você também vê o tempo todo a alternância em outros contextos, como gramáticas, e parece que está dividindo duas opções.Eu acho que os operadores são muito mais legíveis quando suas funções refletem de perto seu objetivo matemático familiar. Por exemplo, operadores padrão como +, -, etc. são mais legíveis que Adicionar ou Subtrair ao executar adição e subtração. O comportamento do operador deve ser claramente definido. Eu posso tolerar uma sobrecarga do significado de + para concatenação de lista, por exemplo. Idealmente, a operação não teria efeitos colaterais, retornando um valor em vez de sofrer mutação.
Mas lutei com operadores de atalho para funções como dobra. Obviamente, mais experiência com eles facilitaria isso, mas acho
foldRight
mais legível que isso/:
.fonte
fold
frequência suficiente para que um operador economize muito tempo. Também pode ser por isso que o LISPy (+ 1 2 3) parece estranho, mas (adicione 1 2 3) é menos. Tendemos a pensar nos operadores como estritamente binários. Os>>.
operadores de estilo do Parsec podem ser bobos, mas pelo menos a principal coisa que você faz no Parsec é combinar analisadores, para que os operadores utilizem muito.O problema com os operadores é que há apenas um pequeno número deles em comparação com o número de nomes de métodos sensitivos (!) Que poderiam ser usados. Um efeito colateral é que os operadores definidos pelo usuário tendem a introduzir muita sobrecarga.
Certamente, a linha
C = A * x + b * c
é mais fácil de escrever e ler do queC = A.multiplyVector(x).addVector(b.multiplyScalar(c))
.Ou, certamente, é mais fácil escrever se você tiver todas as versões sobrecarregadas de todos esses operadores em sua cabeça. E você pode lê-lo depois, enquanto corrige o penúltimo bug.
Agora, para a maioria dos códigos que estão sendo executados por aí, tudo bem. "O projeto passa em todos os testes e é executado" - para muitos softwares que é tudo o que podemos desejar.
Mas as coisas funcionam de maneira diferente quando você está criando um software que entra em áreas críticas. Material crítico de segurança. Segurança. Software que mantém aviões no ar ou que mantém instalações nucleares funcionando. Ou software que criptografa seus e-mails altamente confidenciais.
Para especialistas em segurança especializados em auditoria de código, isso pode ser um pesadelo. A combinação de uma abundância de operadores definidos pelo usuário e sobrecarga de operadores pode deixá-los na situação desagradável de que eles têm dificuldade em descobrir qual código será executado eventualmente.
Portanto, como afirmado na pergunta original, este é um assunto altamente subjetivo. E enquanto muitas sugestões sobre como os operadores devem ser usados podem parecer perfeitamente sensatas, a combinação de apenas algumas delas pode criar muitos problemas a longo prazo.
fonte
Suponho que o exemplo mais extremo seja o APL, o seguinte programa é o "jogo da vida":
E se você puder entender o que tudo isso significa sem passar alguns dias com o manual de referência, então boa sorte para você!
O problema é que você tem um conjunto de símbolos que quase todo mundo entende intuitivamente:
+,-,*,/,%,=,==,&
e o restante, que requer alguma explicação antes de poder ser entendido e geralmente é específico para o idioma em particular. Por exemplo, não há símbolo óbvio para especificar qual membro de uma matriz você deseja, [], () e até "." foram usados, mas igualmente não há uma palavra-chave óbvia que você possa usar com facilidade; portanto, há um argumento a ser feito para o uso criterioso dos operadores. Mas não muitos deles.
fonte