Por que os operadores definidos pelo usuário não são mais comuns?


94

Um recurso que sinto falta das linguagens funcionais é a ideia de que os operadores são apenas funções; portanto, adicionar um operador personalizado é tão simples quanto adicionar uma função. Muitas linguagens procedurais permitem sobrecargas do operador, portanto, em certo sentido, os operadores ainda são funções (isso é verdade em D, onde o operador é passado como uma string em um parâmetro de modelo).

Parece que onde a sobrecarga do operador é permitida, geralmente é trivial adicionar operadores personalizados adicionais. Encontrei este post no blog , que argumenta que os operadores personalizados não funcionam bem com a notação infix por causa das regras de precedência, mas o autor fornece várias soluções para esse problema.

Olhei em volta e não consegui encontrar nenhuma linguagem processual que suporte operadores personalizados na linguagem. Existem hacks (como macros em C ++), mas isso não é o mesmo que o suporte a idiomas.

Como esse recurso é bastante trivial de implementar, por que não é mais comum?

Eu entendo que isso pode levar a algum código feio, mas isso não impediu os designers de linguagem de adicionar recursos úteis que podem ser facilmente abusados ​​(macros, operador ternário, ponteiros inseguros).

Casos de uso reais:

  • Implementar operadores ausentes (por exemplo, Lua não possui operadores bit a bit)
  • Imitar D's ~(concatenação de matriz)
  • DSLs
  • Use |como açúcar de sintaxe no estilo de canal Unix (usando coroutines / geradores)

Eu também estou interessado em idiomas que fazer permitir que os operadores de costume, mas eu estou mais interessado em por que ele foi excluído. Pensei em criar uma linguagem de script para adicionar operadores definidos pelo usuário, mas me parei quando percebi que não a havia visto em nenhum lugar; portanto, provavelmente há uma boa razão pela qual designers de linguagem mais inteligentes do que eu não o permitiram.


4
Há uma discussão no Reddit sobre essa questão.
Dynamic

2
@dimatura: Eu não sei muito sobre R, mas seus operadores personalizados ainda não parecem muito flexíveis (por exemplo, nenhuma maneira adequada de definir fixidez, escopo, sobrecarga etc.), o que explica por que eles não são muito usados. Isso é diferente em outros idiomas, certamente em Haskell, que usa muito operadores de infix personalizados . Outro exemplo de uma linguagem procedural com suporte a infix razoavelmente personalizado é o Nimrod e, é claro, o Perl também permite .
leftaroundabout

4
Existe a outra opção: o Lisp na verdade não tem diferença entre operadores e funções.
BeardedO

4
Nenhuma menção ao Prolog ainda, outra linguagem em que os operadores são apenas um açúcar sintático para funções (sim, mesmo as de matemática) e que permitem definir operadores personalizados com precedência personalizada.
David Cowden

2
@ BeardedO, não há operadores de infix no Lisp. Depois de apresentá-los, você terá que lidar com todos os problemas com precedência e coisas do tipo.
SK-logic

Respostas:


134

Existem duas escolas de pensamento diametralmente opostas no design da linguagem de programação. Uma é que os programadores escrevem um código melhor com menos restrições e a outra é que eles escrevem um código melhor com mais restrições. Na minha opinião, a realidade é que bons programadores experientes florescem com menos restrições, mas essas restrições podem beneficiar a qualidade do código dos iniciantes.

Operadores definidos pelo usuário podem criar código muito elegante em mãos experientes e código absolutamente horrível para iniciantes. Portanto, se o seu idioma os inclui ou não, depende da escola de pensamento do seu designer de idiomas.


23
Nas duas escolas de pensamento, plus.google.com/110981030061712822816/posts/KaSKeg4vQtz também marcou +1 por ter a resposta mais direta (e provavelmente a mais verdadeira) à pergunta de por que os designers de idiomas escolhem um versus o outro. Também gostaria de dizer com base na sua tese você pode extrapolar que menos idiomas permitem isso porque uma parcela menor de desenvolvedores são altamente qualificados do que o contrário (por cento superiores de qualquer coisa será sempre uma minoria, por definição)
Jimmy Hoffa

12
Eu acho que essa resposta é vaga e apenas marginalmente relacionada à pergunta. (Eu também acontecer a pensar que a sua caracterização é errado, uma vez que contradiz a minha experiência, mas isso não é importante.)
Konrad Rudolph

1
Como o @KonradRudolph disse, isso realmente não responde à pergunta. Pegue uma linguagem como o Prolog, que permite fazer o que quiser com os operadores, incluindo definir sua precedência quando infix ou prefixo colocado. Eu não acho que o fato de que você possa escrever operadores personalizados tenha algo a ver com o nível de habilidade do público-alvo, mas porque o objetivo do Prolog é ler o mais logicamente possível. A inclusão de operadores personalizados permite que você escreva programas que lêem muito logicamente (afinal, um programa de prólogo é apenas um monte de instruções lógicas).
David13

Como eu perdi aquele discurso de stevey? Oh cara, marcado como favorito
George Mauer

Em que filosofia eu cairei se achar que os programadores provavelmente escreverão o melhor código se as linguagens lhes derem as ferramentas para dizer quando o compilador deve fazer várias inferências (por exemplo, argumentos de boxe automático para um Formatmétodo) e quando deve recusar ( por exemplo, argumentos de caixa automática para ReferenceEquals). Quanto mais a linguagem da habilidade permitir que os programadores digam quando determinadas inferências seriam inadequadas, mais seguramente ela pode oferecer inferências convenientes quando apropriado.
Supercat 24/02

83

Dada a opção entre concatenar matrizes com ~ ou com "myArray.Concat (secondArray)", eu provavelmente preferiria o último. Por quê? Porque ~ é um personagem completamente sem sentido que só tem seu significado - o da concatenação de array - dado no projeto específico em que foi escrito.

Basicamente, como você disse, os operadores não são diferentes dos métodos. Porém, embora os métodos possam receber nomes legíveis e compreensíveis que aumentam a compreensão do fluxo de código, os operadores são opacos e situacionais.

É por isso que eu também não gosto do .operador do PHP (concatenação de strings) ou da maioria dos operadores do Haskell ou OCaml, embora, neste caso, alguns padrões universalmente aceitos estejam surgindo para linguagens funcionais.


27
O mesmo poderia ser dito de todos os operadores. O que os torna bons e também podem tornar os operadores definidos pelo usuário bons (se eles são definidos criteriosamente), é o seguinte: Apesar do caractere usado para eles serem apenas um caractere, o uso generalizado e possivelmente as propriedades mnemônicas fazem seu significado no código-fonte imediatamente óbvio para aqueles que estão familiarizados com isso. Portanto, sua resposta como está não é tão IMHO convincente.

33
Como mencionei no final, alguns operadores têm reconhecimento universal (como os símbolos aritméticos padrão, mudanças bit a bit, AND / OR e assim por diante) e, portanto, sua discrepância supera sua opacidade. Mas ser capaz de definir operadores arbitrários parece manter o pior dos dois mundos.
Avner Shahar-Kashtan

11
Então, você também é a favor de proibir, por exemplo, nomes de uma letra no nível do idioma? De maneira mais geral, não permitindo nada no idioma que pareça fazer mais mal do que bem? Essa é uma abordagem válida para o design de linguagem, mas não a única, então não acho que você não possa pressupor isso.

9
Não concordo que os operadores personalizados estejam "adicionando" um recurso, mas removendo uma restrição. Os operadores geralmente são apenas funções; portanto, em vez de uma tabela de símbolos estáticos para operadores, uma tabela dinâmica e dependente do contexto pode ser usada. Eu imagino que é assim que as sobrecargas do operador são tratadas, porque +e <<certamente não estão definidas Object(eu recebo "nenhuma correspondência para o operador + em ..." ao fazer isso em uma classe simples em C ++).
Beatgammit 29/12/12

10
O que leva muito tempo para descobrir é que a codificação geralmente não é fazer com que um computador faça algo por você, mas sim gerar um documento que pessoas e computadores possam ler, sendo as pessoas um pouco mais importantes que os computadores. Esta resposta está exatamente correta: se a próxima pessoa (ou você em 2 anos) tiver que gastar 10 segundos tentando descobrir o que ~ significa, você também pode ter passado os 10 segundos digitando uma chamada de método.
Bill K

71

Como esse recurso é bastante trivial de implementar, por que não é mais comum?

Sua premissa está errada. É não “bastante trivial para implementar”. De fato, isso traz um monte de problemas.

Vamos dar uma olhada nas "soluções" sugeridas no post:

  • Sem precedência . O próprio autor diz que "não usar regras de precedência simplesmente não é uma opção".
  • Análise semântica consciente . Como o artigo diz, isso exigiria que o compilador tivesse muito conhecimento semântico. O artigo não oferece realmente uma solução para isso e deixe-me dizer, isso simplesmente não é trivial. Os compiladores são projetados como uma troca entre potência e complexidade. Em particular, o autor menciona uma etapa de pré-análise para coletar as informações relevantes, mas a pré-análise é ineficiente e os compiladores se esforçam muito para minimizar os passes de análise.
  • Nenhum operador de infixo personalizado . Bem, isso não é uma solução.
  • Solução híbrida . Essa solução traz muitas (mas não todas) as desvantagens da análise semântica consciente. Em particular, como o compilador deve tratar os tokens desconhecidos como potencialmente representando operadores personalizados, geralmente não é possível produzir mensagens de erro significativas. Também pode exigir que a definição do referido operador continue com a análise (para coletar informações de tipo etc.), mais uma vez necessitando de um passe de análise adicional.

Em suma, esse é um recurso caro para implementar, tanto em termos de complexidade do analisador quanto de desempenho, e não está claro se isso traria muitos benefícios. Certamente, existem alguns benefícios na capacidade de definir novos operadores, mas mesmo aqueles são controversos (basta olhar para as outras respostas argumentando que ter novos operadores não é uma coisa boa).


2
Obrigado pela voz da sanidade. Minha experiência sugere que as pessoas rejeitando as coisas como trivial ou são peritos ou alegremente ignorante, e mais frequentemente na última categoria: x
Matthieu M.

14
cada um desses problemas foi resolvido em idiomas que existem há 20 anos ...
Philip JF

11
E, no entanto, é excepcionalmente trivial de implementar. Esqueça todos os algoritmos de análise de livros de dragões, é do século 21 e é hora de seguir em frente. Até mesmo estender uma sintaxe de linguagem é fácil, e adicionar operadores de uma determinada precedência é trivial. Veja, digamos, analisador Haskell. É muito, muito mais simples que analisadores para os idiomas inchados "tradicionais".
SK-logic

3
@ SK-logic Sua afirmação geral não me convence. Sim, a análise avançou. Não, implementar precedências arbitrárias de operadores dependendo do tipo não é "trivial". Produzir boas mensagens de erro com contexto limitado não é "trivial". Produzir analisadores eficientes com idiomas que exigem várias passagens para transformar não é viável.
Konrad Rudolph

6
No Haskell, a análise é "fácil" (comparada à maioria dos idiomas). A precedência é independente do contexto. A parte que fornece as mensagens de erro está relacionada aos recursos do sistema de tipos de letra e tipo avançado, e não aos operadores definidos pelo usuário. É a sobrecarga, não a definição do usuário, que é o difícil problema.
Philip JF

25

Vamos ignorar o argumento "operadores abusam para prejudicar a legibilidade" no momento e focar nas implicações do design da linguagem.

Os operadores de infix têm mais problemas do que simples regras de precedência (embora seja franco, o link que você faz referência trivializa o impacto dessa decisão de design). Uma é a resolução de conflitos: o que acontece quando você define a.operator+(b)e b.operator+(a)? Preferir um sobre o outro leva a quebrar a propriedade comutativa esperada desse operador. Lançar um erro pode levar a módulos que, de outra forma, funcionariam, seriam quebrados uma vez juntos. O que acontece quando você começa a lançar tipos derivados no mix?

O fato é que os operadores não são apenas funções. As funções são independentes ou pertencem à sua classe, o que fornece uma preferência clara sobre qual parâmetro (se houver) possui o despacho polimórfico.

E isso ignora os vários problemas de embalagem e resolução que surgem dos operadores. A razão pela qual os designers de idiomas (em geral) limitam a definição de operador de infixo é porque ele cria uma pilha de problemas para o idioma, ao mesmo tempo em que oferece benefícios discutíveis.

E, francamente, porque eles não são triviais para implementar.


1
Acredito que esse seja o motivo pelo qual o Java não os inclui, mas as linguagens que os possuem possuem regras claras de precedência. Eu acho que é semelhante à multiplicação de matrizes. Não é comutativo, então você deve estar ciente quando estiver usando matrizes. Acho que o mesmo se aplica ao lidar com aulas, mas sim, concordo que ter um não comutativo +é mau. Mas isso é realmente um argumento contra operadores definidos pelo usuário? Parece um argumento contra a sobrecarga do operador em geral.
Beatgammit 29/12/12

1
@tjameson - Sim, os idiomas têm regras claras para precedência, para matemática . As regras de precedência para operações matemáticas provavelmente não se aplicarão realmente se os operadores estiverem sendo sobrecarregados para coisas assim boost::spirit. Assim que você permitir que os operadores definidos pelo usuário piorem, não há uma boa maneira de definir bem a precedência para a matemática. Escrevi um pouco sobre isso no contexto de uma linguagem que procura especificamente resolver problemas com operadores definidos arbitrariamente.
Telastyn

22
Eu chamo besteira, senhor. Existe vida fora do gueto OO e funções não pertencem necessariamente a nenhum objeto; portanto, encontrar a função correta é exatamente o mesmo que encontrar o operador certo.
Matthieu M.

1
@matthieuM. Certamente. As regras de propriedade e expedição são mais uniformes nos idiomas que não suportam funções de membro. No momento, porém, a pergunta original se torna 'por que as línguas não OO são mais comuns?' que é um jogo totalmente diferente.
Telastyn

11
Você já viu como a Haskell faz operadores personalizados? Eles funcionam exatamente como funções normais, exceto que eles também têm uma precedência associada. (De fato, o mesmo ocorre com as funções normais, portanto, mesmo lá, elas realmente não diferem.) Basicamente, os operadores são infix por padrão e os nomes são prefixo, mas essa é a única diferença.
Tikhon Jelvis

19

Acho que você ficaria surpreso com a frequência com que as sobrecargas do operador são implementadas de alguma forma. Mas eles não são comumente usados ​​em muitas comunidades.

Por que usar ~ para concatenar para uma matriz? Por que não usar << como Ruby ? Como os programadores com os quais você trabalha provavelmente não são programadores Ruby. Ou programadores D. Então, o que eles fazem quando encontram seu código? Eles precisam procurar o significado do símbolo.

Eu costumava trabalhar com um desenvolvedor de C # muito bom, que também gostava de linguagens funcionais. Do nada, ele começou a introduzir mônadas no C # por meio de métodos de extensão e usando a terminologia padrão das mônadas. Ninguém poderia contestar que parte do código dele era mais tenso e ainda mais legível quando você sabia o que significava, mas isso significava que todos tinham que aprender a terminologia da mônada antes que o código fizesse sentido .

Justo, você acha? Era apenas uma equipe pequena. Pessoalmente, eu discordo. Todo novo desenvolvedor estava destinado a ser confundido com essa terminologia. Não temos problemas suficientes para aprender um novo domínio?

Por outro lado, utilizarei alegremente o ??operador em C # porque espero que outros desenvolvedores de C # saibam o que é, mas não o sobrecarregaria em um idioma que não o suporta por padrão.


Não entendo o seu exemplo Double.NaN, esse comportamento faz parte da especificação de ponto flutuante e é suportado em todos os idiomas que usei. Você está dizendo que os operadores personalizados não são suportados porque isso confundiria os desenvolvedores que não conhecem o recurso? Parece o mesmo argumento contra o uso do operador ternário ou o seu ??exemplo.
Beatgammit 29/12/12

@tjameson: Você está certo, na verdade, isso foi um pouco tangencial ao ponto que eu estava tentando enfatizar, e não particularmente bem escrito. Eu pensei no ?? exemplo como eu estava escrevendo isso e prefiro esse exemplo. Removendo o parágrafo double.NaN.
Pd

6
Eu acho que seu ponto "todo novo desenvolvedor estava destinado a ser confundido com essa terminologia" é um pouco sem graça, preocupar-me com a curva de aprendizado de novos desenvolvedores para sua base de código é para mim o mesmo que me preocupar com a curva de aprendizado de novos desenvolvedores para um nova versão do .NET. Eu acho que o mais importante é quando os desenvolvedores o aprendem (novo .NET ou sua base de código), isso faz sentido e mostra valor? Nesse caso, a curva de aprendizado vale a pena, porque cada desenvolvedor precisa aprendê-la apenas uma vez, embora o código precise ser manipulado inúmeras vezes; portanto, se ele melhorar o código, será um custo menor.
Jimmy Hoffa

7
@JimmyHoffa É um custo recorrente. Pago antecipadamente a cada novo desenvolvedor e também afeta os desenvolvedores existentes porque eles precisam dar suporte. Também há um custo de documentação e um elemento de risco - parece seguro o suficiente hoje, mas daqui a 30 anos, todos terão mudado, o idioma e o aplicativo agora são "legados", a documentação é uma enorme pilha de vapor e algum otário pobre ficará arrancando os cabelos com o brilho de programadores que eram preguiçosos demais para digitar ".concat ()". O valor esperado é suficiente para compensar os custos?
Sylverdrag

3
Além disso, o argumento "Todo novo desenvolvedor estava destinado a ser confundido com essa terminologia. Não temos problemas suficientes para aprender um novo domínio?" poderia ter sido aplicado ao OOP nos anos 80, programação estruturada antes disso, ou IoC há 10 anos. É hora de parar de usar esse argumento falacioso.
Mauricio Scheffer

11

Eu posso pensar em alguns motivos:

  • Eles não são triviais de implementar - permitindo que operadores personalizados arbitrários possam tornar seu compilador muito mais complexo, especialmente se você permitir regras de precedência, fixidez e aridade definidas pelo usuário. Se a simplicidade é uma virtude, a sobrecarga do operador está afastando o bom design de linguagem.
  • Eles são abusados - principalmente por codificadores que acham "legal" redefinir os operadores e começar a redefini-los para todos os tipos de classes personalizadas. Em pouco tempo, seu código está repleto de uma carga de símbolos personalizados que ninguém mais pode ler ou entender porque os operadores não seguem as regras bem conhecidas. Eu não compro o argumento "DSL", a menos que seu DSL seja um subconjunto da matemática :-)
  • Eles prejudicam a legibilidade e a capacidade de manutenção - se os operadores são substituídos regularmente, pode ser difícil identificar quando esse recurso está sendo usado, e os codificadores são forçados a se perguntar continuamente o que um operador está fazendo. É muito melhor fornecer nomes de função significativos. Digitar alguns caracteres extras é barato, problemas de manutenção a longo prazo são caros.
  • Eles podem quebrar as expectativas implícitas de desempenho . Por exemplo, eu normalmente esperaria que a pesquisa de um elemento em uma matriz fosse O(1). Porém, com sobrecarga do operador, someobject[i]pode ser facilmente uma O(n)operação, dependendo da implementação do operador de indexação.

Na realidade, existem muito poucos casos em que a sobrecarga do operador tem usos justificáveis ​​em comparação ao uso de funções regulares. Um exemplo legítimo pode estar projetando uma classe numérica complexa para ser usada por matemáticos, que entendem as maneiras bem compreendidas pelas quais os operadores matemáticos são definidos para números complexos. Mas esse realmente não é um caso muito comum.

Alguns casos interessantes a serem considerados:

  • Lisps : em geral, não fazem distinção entre operadores e funções - +é apenas uma função regular. Você pode definir as funções da maneira que desejar (normalmente, há uma maneira de defini-las em espaços para nome separados para evitar conflitos com o interno +), incluindo operadores. Mas há uma tendência cultural de usar nomes de funções significativos, para que isso não seja muito abusado. Além disso, na notação de prefixo Lisp tende a ser usada exclusivamente, portanto, há menos valor no "açúcar sintático" que as sobrecargas do operador fornecem.
  • Java - não permite sobrecarga do operador. Ocasionalmente, isso é irritante (para coisas como o caso do número Complex), mas, em média, é provavelmente a decisão de design certa para Java, que se destina a ser uma linguagem simples e OOP de uso geral. O código Java é realmente muito fácil para os desenvolvedores com pouca / média habilidade manterem como resultado dessa simplicidade.
  • C ++ possui sobrecarga de operador muito sofisticada. Às vezes, isso é abusado ( cout << "Hello World!"alguém?), Mas a abordagem faz sentido, dado o posicionamento do C ++ como uma linguagem complexa que permite programação de alto nível e, ao mesmo tempo, permite que você fique muito próximo do metal para desempenho, para que você possa, por exemplo, escrever uma classe numérica complexa que se comporte exatamente como você deseja, sem comprometer o desempenho. Entende-se que é de sua responsabilidade se você atirar no próprio pé.

8

Como esse recurso é bastante trivial de implementar, por que não é mais comum?

Não é trivial de implementar (a menos que seja trivialmente implementado). Também não é muito útil, mesmo se implementado de maneira ideal: os ganhos de legibilidade com a dispersão são compensados ​​pelas perdas de legibilidade por falta de familiaridade e opacidade. Em resumo, é incomum, porque geralmente não vale o tempo dos desenvolvedores ou dos usuários.

Dito isto, posso pensar em três idiomas que o fazem, e eles fazem isso de maneiras diferentes:

  • O raquete, um esquema, quando não está sendo toda a expressão S, permite e espera que você escreva o que equivale ao analisador de qualquer sintaxe que você deseja estender (e fornece ganchos úteis para torná-lo tratável).
  • Haskell, uma linguagem de programação puramente funcional, permite definir qualquer operador que consiste apenas de pontuação e permite fornecer um nível de fixidez (10 disponível) e uma associatividade. Os operadores ternários etc. podem ser criados a partir de operadores binários e funções de ordem superior.
  • A Agda, uma linguagem de programação tipicamente dependente, é extremamente flexível com os operadores (documento aqui ), permitindo que ambos, se-então e se-então-outro, sejam definidos como operadores no mesmo programa, mas seu lexer, analisador e avaliador são todos fortemente acoplados como um resultado.

4
Você disse que "não é trivial" e listou imediatamente três idiomas com algumas implementações escandalosamente triviais. Então, é bastante trivial, afinal, não é?
SK-logic

7

Uma das principais razões pelas quais os operadores personalizados são desencorajados é porque qualquer operador pode significar / pode fazer qualquer coisa.

Por exemplo cstream, a sobrecarga de turno à esquerda muito criticada.

Quando um idioma permite sobrecargas do operador, geralmente há um incentivo para manter o comportamento do operador semelhante ao comportamento da base para evitar confusão.

Os operadores definidos pelo usuário também tornam a análise muito mais difícil, especialmente quando também há regras de preferências personalizadas.


6
Não vejo como as partes sobre sobrecarga de operadores se aplicam à definição de operadores completamente novos.

Eu vi um uso muito bom da sobrecarga de operadores (bibliotecas de álgebra linear) e muito ruim, como você mencionou. Eu não acho que este seja um bom argumento contra operadores personalizados. A análise pode ser um problema, mas é bastante óbvio quando um operador é esperado. Após a análise, cabe ao gerador de código procurar o significado do operador.
Beatgammit 29/12/12

3
E como isso é diferente da sobrecarga de método?
Jörg W Mittag

@ JörgWMittag com sobrecarga de método, existe (na maioria das vezes) um nome significativo anexado. com um símbolo que é mais difícil de explicar sucintamente o que vai acontecer
catraca aberração

1
@ratchetfreak: Bem. +adicione duas coisas, -subtraia-as, *multiplique-as. Meu sentimento é que ninguém obriga o programador a fazer com que a função / método addrealmente adicione algo e doNothingpossa lançar armas nucleares. E a.plus(b.minus(c.times(d)).times(e)é muito menos legível, então a + (b - c * d) * e(bônus adicional - onde na primeira picada há erro na transcrição). Eu não vejo como primeira é mais significativa ...
Maciej Piechotka

4

Não usamos operadores definidos pelo usuário pelo mesmo motivo que não usamos palavras definidas pelo usuário. Ninguém chamaria sua função "sworp". A única maneira de transmitir seu pensamento a outra pessoa é usar um idioma compartilhado. E isso significa que palavras e sinais (operadores) devem ser conhecidos da sociedade para a qual você está escrevendo seu código.

Portanto, os operadores que você vê em uso nas linguagens de programação são os que aprendemos na escola (aritmética) ou os que foram estabelecidos na comunidade de programação, como, por exemplo, operadores booleanos.


1
Acrescente a isso a capacidade de descobrir o significado por trás de uma função. No caso do "sworp", você pode colocá-lo facilmente em uma caixa de pesquisa e encontrar a documentação. Colocar um operador em um mecanismo de pesquisa é um parque totalmente diferente. Nossos mecanismos de pesquisa atuais simplesmente são péssimos em encontrar operadores, e eu perdi muito tempo tentando encontrar o significado em alguns casos.
Mark H

1
Quanto "escola" você conta? Por exemplo, acho que elemé uma ótima idéia e certamente um operador que todos deveriam entender, mas outros parecem discordar.
Tikhon Jelvis 31/12/12

1
Não é o problema com o símbolo. É o problema com o teclado. Por exemplo, eu uso o emacs haskell-mode com o embelezador unicode ativado. E converte automaticamente operadores ascii em símbolos unicode. Mas eu não seria capaz de digitá-lo se não houvesse um equivalente ascii.
Vagif Verdi

2
As línguas naturais são construídas apenas com as "palavras definidas pelo usuário" e nada mais. E alguns idiomas realmente incentivam a formação de palavras personalizada.
SK-logic

4

Quanto às linguagens que suportam essa sobrecarga: Scala, na verdade, de uma maneira muito mais limpa e melhor pode C ++. A maioria dos caracteres pode ser usada em nomes de funções, para que você possa definir operadores como! + * = ++, se desejar. Há suporte interno para infix (para todas as funções que usam um argumento). Eu acho que você também pode definir a associatividade de tais funções. Você não pode, no entanto, definir a precedência (apenas com truques feios, veja aqui ).


4

Uma coisa que ainda não foi mencionada é o caso do Smalltalk, onde tudo (incluindo operadores) é uma mensagem enviada. "Operadores" gostam +, |e assim por diante, são realmente métodos unários.

Todos os métodos podem ser substituídos, o que a + bsignifica adição de número inteiro se ae bsão ambos números inteiros e significa adição de vetor se ambos forem OrderedCollections.

Não há regras de precedência, pois são apenas chamadas de método. Isso tem uma implicação importante para a notação matemática padrão: 3 + 4 * 5significa (3 + 4) * 5, não 3 + (4 * 5).

(Este é um grande obstáculo para os iniciantes no Smalltalk. A quebra das regras da matemática remove um caso especial, para que toda a avaliação do código prossiga uniformemente da esquerda para a direita, tornando a linguagem muito mais simples.


3

Você está lutando contra duas coisas aqui:

  1. Por que os operadores existem em idiomas em primeiro lugar?
  2. Qual é a virtude dos operadores sobre funções / métodos?

Na maioria dos idiomas, os operadores não são realmente implementados como funções simples. Eles podem ter algum andaime de função, mas o compilador / tempo de execução está explicitamente ciente de seu significado semântico e de como traduzi-los eficientemente no código da máquina. Isso é muito mais verdadeiro, mesmo quando comparado às funções internas (é por isso que a maioria das implementações também não inclui toda a sobrecarga de chamada de função em sua implementação). A maioria dos operadores é abstração de nível superior em instruções primitivas encontradas em CPUs (razão pela qual em parte a maioria dos operadores é aritmética, booleana ou bit a bit). Você pode modelá-las como funções "especiais" (chamá-las de "primitivas" ou "builtins" ou "nativas" ou o que for), mas fazer isso genericamente requer um conjunto de semânticas muito robustas para definir essas funções especiais. A alternativa é ter operadores internos que semanticamente se parecem com operadores definidos pelo usuário, mas que, de outra forma, invocam caminhos especiais no compilador. Isso está em conflito com a resposta à segunda pergunta ...

Além do problema de tradução automática que mencionei acima, em um nível sintático, os operadores não são realmente diferentes das funções. Suas características distintivas tendem a ser concisas e simbólicas, o que sugere uma característica adicional significativa que eles devem ter para serem úteis: eles devem ter entendido amplamente o significado / semântica para os desenvolvedores. Símbolos curtos não transmitem muito significado, a menos que seja uma mão curta para um conjunto de semânticas que já são entendidas. Isso torna os operadores definidos pelo usuário inerentemente inúteis, pois, por sua própria natureza, eles não são tão amplamente compreendidos. Eles fazem tanto sentido quanto os nomes de função de uma ou duas letras.

As sobrecargas do operador do C ++ fornecem terreno fértil para examinar isso. A maioria dos "abusos" de sobrecarga de operador ocorre na forma de sobrecargas que quebram parte do contrato semântico amplamente compreendido (um exemplo clássico é uma sobrecarga de operador + de modo que a + b! = B + a, ou onde + modifica um de seus operandos).

Se você observar o Smalltalk, que permite sobrecarga de operador e operadores definidos pelo usuário, você poderá ver como um idioma pode fazê-lo e qual a utilidade dele. No Smalltalk, os operadores são meramente métodos com propriedades sintáticas diferentes (a saber, eles são codificados como binário infix). A linguagem usa "métodos primitivos" para operadores e métodos especiais acelerados. Você descobre que poucos, se houver operadores definidos pelo usuário, são criados e, quando o são, tendem a não se acostumar tanto quanto o autor provavelmente pretendeu que fossem usados. Mesmo o equivalente a uma sobrecarga de operador é raro, porque é principalmente uma perda líquida definir uma nova função como operador em vez de método, pois o último permite a expressão da semântica da função.


1

Sempre achei as sobrecargas de operadores em C ++ um atalho conveniente para uma equipe de desenvolvedor único, mas que causa todo tipo de confusão a longo prazo, simplesmente porque as chamadas de método estão sendo "ocultas" de uma maneira que não é fácil. para ferramentas como doxygen se separarem, e as pessoas precisam entender os idiomas para utilizá-los adequadamente.

Às vezes, é muito mais difícil entender do que você esperaria, até. Era uma vez, em um grande projeto C ++ de plataforma cruzada, decidi que seria uma boa idéia normalizar a maneira como os caminhos eram construídos criando um FilePathobjeto (semelhante ao Fileobjeto de Java ), que teria operador / usado para concatenar outro parte do caminho (para que você possa fazer algo como File::getHomeDir()/"foo"/"bar"e faria a coisa certa em todas as nossas plataformas suportadas). Todo mundo que via isso dizia essencialmente: "Que diabos? Divisão de cordas? ... Oh, isso é fofo, mas eu não confio que faça a coisa certa".

Da mesma forma, há muitos casos em programação gráfica ou em outras áreas em que a matemática de vetor / matriz acontece muito, e é tentador fazer coisas como Matrix * Matrix, Vector * Vector (ponto), Vector% Vector (cross), Matrix * Vector ( transformação de matriz), vetor Matrix ^ (transformação de matriz de caso especial ignorando a coordenada homogênea - útil para normais de superfície) e assim por diante, mas enquanto economiza um pouco de tempo de análise para a pessoa que escreveu a biblioteca de matemática de vetores, ela só termina confundir a questão mais com os outros. Simplesmente não vale a pena.


0

Sobrecargas de operador são uma má idéia pelo mesmo motivo que sobrecargas de método são uma má idéia: o mesmo símbolo na tela teria significados diferentes, dependendo do que está ao seu redor. Isso dificulta a leitura casual.

Como a legibilidade é um aspecto crítico da capacidade de manutenção, você sempre deve evitar sobrecargas (exceto em alguns casos muito especiais). É muito melhor para cada símbolo (seja operador ou identificador alfanumérico) ter um significado único que seja autônomo.

Para ilustrar: ao ler código desconhecido, se você encontrar um novo identificador alfanum desconhecido, pelo menos terá a vantagem de saber que não o conhece.. Você pode então procurar. Se, no entanto, você vir um identificador ou operador comum do qual saiba o significado, é bem menos provável que note que ele foi sobrecarregado para ter um significado completamente diferente. Para saber quais operadores foram sobrecarregados (em uma base de código que utilizou amplamente a sobrecarga), você precisaria de um conhecimento prático do código completo, mesmo se você quiser apenas ler uma pequena parte dele. Isso dificultaria a atualização de novos desenvolvedores sobre esse código e seria impossível atrair pessoas para um pequeno trabalho. Isso pode ser bom para a segurança do trabalho do programador, mas se você for responsável pelo sucesso da base de código, evite essa prática a todo custo.

Como os operadores são pequenos, sobrecarregar os operadores permitiria um código mais denso, mas tornar o código denso não é um benefício real. Uma linha com o dobro da lógica leva o dobro do tempo para ler. O compilador não se importa. A única questão é a legibilidade humana. Como tornar o código compacto não melhora a legibilidade, não há benefício real na compactação. Vá em frente, ocupe o espaço e forneça às operações exclusivas um identificador exclusivo, e seu código terá mais sucesso a longo prazo.


"o mesmo símbolo na tela teria significados diferentes dependendo do que o rodeia" - já é o caso de muitos operadores na maioria dos idiomas.
Rkj

-1

Dificuldades técnicas para lidar com a precedência e a análise complexa de lado, acho que existem alguns aspectos do que é uma linguagem de programação que deve ser considerada.

Os operadores são tipicamente construções lógicas curtas, bem definidas e documentadas no idioma principal (comparar, atribuir ..). Eles também são geralmente difíceis de entender sem documentação (compare a^bcom, xor(a,b)por exemplo). Há um número bastante limitado de operadores que realmente podem fazer sentido na programação normal (>, <, =, + etc.).

Minha ideia é que é melhor manter um conjunto de operadores bem definidos em um idioma - depois permitir a sobrecarga do operador desses operadores (dada uma recomendação simples de que os operadores façam a mesma coisa, mas com um tipo de dados personalizado).

Seus casos de uso ~e |seriam realmente possíveis com sobrecarga simples do operador (C #, C ++ etc.). DSL é uma área de uso válida, mas provavelmente uma das únicas áreas válidas (do meu ponto de vista). Eu, no entanto, acho que existem ferramentas melhores para criar novos idiomas. A execução de uma linguagem DSL verdadeira em outra linguagem não é tão difícil usando qualquer uma dessas ferramentas de compilador-compilador. O mesmo vale para o "argumento LUA estendido". Um idioma é provavelmente definido principalmente para resolver problemas de uma maneira específica, não para ser uma base para sub-idiomas (existem exceções).


-1

Outro fator é que nem sempre é fácil definir uma operação com os operadores disponíveis. Quero dizer, sim, para qualquer tipo de número, o operador '*' pode fazer sentido e geralmente é implementado no idioma ou nos módulos existentes. Mas no caso das classes complexas típicas que você precisa definir (coisas como ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter, etc) esse comportamento não é claro ... O que significa adicionar ou subtrair um número a um Endereço? Multiplique dois endereços?

Claro, você pode definir que adicionar uma sequência a uma classe ShippingAddress significa uma operação personalizada como "substituir a linha 1 no endereço" (em vez da função "setLine1") e adicionar um número é "substituir o código postal" (em vez de "setZipCode") , mas o código não é muito legível e confuso. Normalmente pensamos que o operador é usado em tipos / classes básicos, pois seu comportamento é intuitivo, claro e consistente (quando você estiver familiarizado com o idioma, pelo menos). Pense em tipos como Inteiro, String, ComplexNumbers, etc.

Portanto, mesmo que a definição de operadores possa ser muito útil em alguns casos específicos, sua implementação no mundo real é bastante limitada, pois os 99% dos casos em que essa será uma vitória clara já estão implementados no pacote de idiomas básico.

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.