Os operadores são mais claros de ler do que palavras-chave ou funções? [fechadas]


14

É 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) xVB: CType(x, int32)F #:x :> int32

Em teoria, uma nova linguagem poderia ter operadores para a maioria das funcionalidades internas. Em vez de defou decou varpara declaração de variável, por que não ! nameou @ nameou algo semelhante. Certamente reduz a declaração seguida pela ligação: em @x := 5vez de declare x = 5ou 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?



1
Desejo que um dos 1.000 desenvolvedores que já se queixaram de mim sobre a falta de sobrecarga de operadores do Java responda a essa pergunta.
smp7d

1
Pensei em APL quando terminei de digitar a pergunta, mas de certa forma isso esclarece o ponto e o move para um território um pouco menos subjetivo. Eu acho que a maioria das pessoas concorda que o APL perde algo com facilidade devido ao seu número extremo de operadores, mas a quantidade de pessoas que reclamam quando um idioma não tem sobrecarga de operador certamente fala em favor de algumas funções serem adequadas para atuar como operadores. Entre operadores personalizados proibidos e nada além de operadores, deve ocultar algumas diretrizes para implementação e uso práticos.
CodexArcanum 15/03/12

A meu ver, a falta de sobrecarga do operador é censurável porque privilegia tipos nativos sobre tipos definidos pelo usuário (observe que o Java faz isso de outras maneiras também). Eu sou muito mais ambivalente sobre operadores personalizados Haskell de estilo, que parecem como um convite aberto para problemas ...
comingstorm

2
@comingstorm: Na verdade, acho que o jeito Haskell é melhor. Quando você tem um conjunto finito de operadores que pode sobrecarregar de maneiras diferentes, geralmente é forçado a reutilizar operadores em contextos diferentes (por exemplo, +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.
Tikhon Jelvis 16/03/12

Respostas:


16

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


4
Gostaria de lhe dar um segundo +1 para o link para o artigo Dijkstra.
AProgrammer

Ótimo link, você tinha que publicar uma conta no PayPal! ;)
Adriano Repetti

8

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 = 5lê como "declare x equals 5"ou let x = 5pode 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.


5
Eu não acho @x := 5difícil descobrir - eu ainda leio em voz alta para mim mesmo set x to be equal to 5.
FrustratedWithFormsDesigner

3
@ Rachel, nesse caso, :=tem um uso mais amplo em notação matemática fora das linguagens de programação. Além disso, declarações como x = x + 1se tornam um pouco absurdas.
ccoakley

3
Ironicamente, eu tinha esquecido que algumas pessoas não reconheceriam :=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.
CodexArcanum 15/03/12

1
@ccoakley Obrigado, eu não sabia que :=era usado em matemática. A única definição que encontrei foi "está definida para ser", portanto @x := 5seria traduzida em inglês para "at x is defined to be 5", o que para mim ainda não faz tanto sentido quanto deveria.
Rachel

1
Certamente Pascal usou: = como atribuição, pela falta de uma seta esquerda no ASCII.
Vatine 15/03/12

4

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 * fcom +(+(*(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).


Se os usuários puderem adicionar operadores, por que não deixá-los definir a precedência e a associatividade dos operadores que eles adicionaram?
Tikhon Jelvis 16/03/2012

@TikhonJelvis, era principalmente conservador, mas há alguns aspectos a serem considerados se você quiser usá-lo para qualquer outra coisa além de exemplos. Por exemplo, você deseja vincular a prioridade e a associatividade ao operador, não a um determinado membro do conjunto sobrecarregado (você escolheu o membro após a análise, a escolha não pode influenciar a análise). Assim, para integrar bibliotecas usando o mesmo operador, eles devem concordar com sua prioridade e associatividade. Antes de adicionar a possibilidade, tentaria descobrir por que tão poucos idiomas seguiram o Algol 68 (AFAIK nenhum) e permitiram defini-los.
AProgrammer

Bem, Haskell permite definir operadores arbitrários e definir sua precedência e associatividade. A diferença é que você não pode sobrecarregar os operadores por si só - eles se comportam como funções normais. Isso significa que você não pode ter bibliotecas diferentes tentando usar o mesmo operador para coisas diferentes. Como você pode definir seu próprio operador, há muito menos motivos para reutilizar os mesmos para coisas diferentes.
Tikhon Jelvis

1

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 que A * Bsignifica? Depende inteiramente do contexto: é Aum 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ão and, or, xore not, 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.


4
Pascal não representa uma filosofia diferente. Se você lê os jornais de Wirth, ele usa símbolos para coisas como ande oro 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.
Jerry Coffin

Para adicionar à observação do @JerryCoffin sobre o Wirth, seus idiomas subsequentes (Modula, Oberon) estão usando mais símbolos.
AProgramador

@ AProgrammer: E eles nunca foram a lugar algum. ;)
Mason Wheeler

Penso |(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.
Tikhon Jelvis 15/03/12

1
Uma vantagem das palavras-chave baseadas em letras é que o espaço delas é muito maior que os símbolos. Por exemplo, uma linguagem no estilo Pascal pode incluir operadores para "módulo" e "restante" e para divisão de número inteiro versus divisão de ponto flutuante (que têm significados diferentes dos tipos de seus operandos).
Supercat 24/02

1

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 foldRightmais legível que isso /:.


4
Talvez a frequência de uso também ocorra. Eu não pensaria com foldfrequê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.
CodexArcanum 15/03/12

1

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 que C = 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.


0

Suponho que o exemplo mais extremo seja o APL, o seguinte programa é o "jogo da vida":

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

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.

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.