Por que existem tão poucos idiomas com um operador de tipo variável?

47

Eu falo assim:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

Mas também desta maneira:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

Parece que nenhum idioma tem isso - há uma boa razão para que eles não o façam?

kgongonowdoe
fonte
28
Em geral, o que você deseja fazer seria coberto por todas as linguagens que suportam alguma forma de metaprogramação, com alguns tipos de lambdas, fechamentos ou funções anônimas, que seriam a maneira comum de implementar esses recursos. Com idiomas em que os métodos são cidadãos de primeira classe, você seria capaz de usá-los mais ou menos idênticos às variáveis. Embora não exatamente na sintaxe simples que você usa aqui, pois na maioria desses idiomas, deve ficar claro que você deseja chamar o método armazenado na variável.
22616 Thorsten
7
Os idiomas funcionais do @MartinMaat fazem muito isso.
Thorbjørn Ravn Andersen 23/03
7
em haskell, operadores são funções como qualquer outra função. o tipo de (+)é Num a => a -> a -> aIIRC. você também pode definir funções para que eles possam ser escritos infixada ( a + bem vez de (+) a b)
sara
5
@enderland: sua edição mudou completamente o objetivo da pergunta. Passou de perguntar se existem idiomas, para perguntar por que existem tão poucos. Acho que sua edição resultará em muitos leitores confusos.
Bryan Oakley
5
@enderland: embora isso seja verdade, mudar completamente de assunto serve apenas para confundir. Se estiver fora do tópico, a comunidade votará para fechá-lo. Nenhuma das respostas (no momento em que escrevo isso) faz sentido para a pergunta como está escrita.
22716 Bryan Oakley

Respostas:

104

Operadores são apenas funções sob nomes engraçados, com alguma sintaxe especial por aí.

Em muitas linguagens, tão variadas quanto C ++ e Python, você pode redefinir os operadores substituindo métodos especiais da sua classe. Os operadores padrão (por exemplo +) trabalham de acordo com a lógica que você fornece (por exemplo, concatenando cadeias ou adicionando matrizes ou o que for).

Como essas funções de definição de operador são apenas métodos, você pode transmiti-las como faria com uma função:

# python
action = int.__add__
result = action(3, 5)
assert result == 8

Outros idiomas permitem definir diretamente novos operadores como funções e usá-los no formato infix.

-- haskell
plus a b = a + b  -- a normal function
3 `plus` 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 `action` 3 == 4 -- True

Infelizmente, não tenho certeza se o PHP suporta algo assim, e se o suporte seria uma coisa boa. Use uma função simples, é mais legível que $foo $operator $bar.

9000
fonte
2
@ tac: Sim, isso é legal e pode até ser portado para outros idiomas :) Em relação ao Haskell, o que mais sinto falta é que esse departamento é o $operador que evita parênteses (especialmente vários aninhados) - mas isso só pode funcionar com não -variáveis, excluindo, por exemplo, Python e Java. A composição da função unária da OTOH pode ser bem feita .
9000
6
Eu nunca fui fã de sobrecarga de operadores porque sim, um operador é apenas uma função com sintaxe especial, mas há um contrato implícito que geralmente acompanha os operadores que não acompanham as funções. "+", por exemplo, tem certas expectativas - precedência do operador, comutatividade etc. - e ir contra essas expectativas é um caminho seguro para confundir pessoas e gerar bugs. Uma razão pela qual, embora eu goste de javascript, eu preferiria que eles distinguissem + para adição e concatenação. Perl estava ali.
fool4jesus
4
Observe que o Python possui um operatormódulo padrão que permite escrever action = operator.adde que funcione para qualquer tipo que defina +(não apenas int).
dan04
3
No Haskell, +funciona na classe Typ, para que você possa implementar +qualquer novo tipo de dados criado, mas da mesma maneira que qualquer outra coisa, por exemplo, fmap para um functor. É um ajuste tão natural para Haskell permitir sobrecarregar o operador que eles teriam que trabalhar duro para não permitir!
Martin Capodici 23/03
17
Não sei por que as pessoas estão se fixando na sobrecarga. A pergunta original não é sobre sobrecarga. Parece-me ser sobre operadores como valores de primeira classe. Para escrever $operator1 = +e usar uma expressão, você não precisa sobrecarregar o operador !
Andres F.
16

Existem muitos idiomas que permitem algum tipo de metaprogramação . Surpreende-me, em particular, por não encontrar respostas sobre a família de idiomas Lisp .

Da wikipedia:

Metaprogramação é a escrita de programas de computador com a capacidade de tratar programas como seus dados.

Mais adiante no texto:

O Lisp é provavelmente a linguagem por excelência com recursos de metaprogramação, tanto por sua precedência histórica quanto pela simplicidade e poder de sua metaprogramação.

Línguas Lisp

Uma rápida introdução inventada para Lisp segue.

Uma maneira de ver o código é como um conjunto de instruções: faça isso, depois faça isso e faça outra coisa ... Esta é uma lista! Uma lista de coisas para o programa fazer. E é claro que você pode ter listas dentro de listas para representar loops e assim por diante.

Se nós representamos uma lista contendo os elementos a, b, c, d como este: (ABCD) obtemos algo que se parece com uma chamada de função Lisp, onde aé a função, e b, c, dsão os argumentos. Se fato o típico "Olá Mundo!" programa poderia ser escrito assim:(println "Hello World!")

Obviamente b, cou dpodem ser listas que avaliam algo também. O seguinte: (println "I can add :" (+ 1 3) )imprimiria "" posso adicionar: 4 ".

Portanto, um programa é uma série de listas aninhadas e o primeiro elemento é uma função. A boa notícia é que podemos manipular listas! Para que possamos manipular linguagens de programação.

A vantagem do Lisp

Lisps não são tanto linguagens de programação quanto um kit de ferramentas para criar linguagens de programação. Uma linguagem de programação programável.

Isso não é apenas muito mais fácil no Lisps para criar novos operadores, também é quase impossível escrever alguns operadores em outros idiomas porque os argumentos são avaliados quando passados ​​para a função.

Por exemplo, em uma linguagem C, digamos que você queira escrever um ifoperador, algo como:

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

Nesse caso, os dois argumentos serão avaliados e impressos, em uma ordem dependente da ordem de avaliação dos argumentos.

No Lisps, escrever um operador (chamamos de macro) e escrever uma função são sobre a mesma coisa e são usados ​​da mesma maneira. A principal diferença é que os parâmetros para uma macro não são avaliados antes de serem passados ​​como argumentos para a macro. Isso é essencial para poder escrever alguns operadores, como o ifacima.

Idiomas do mundo real

Mostrando exatamente como está um pouco fora do escopo aqui, mas encorajo você a tentar programar em um Lisp para saber mais. Por exemplo, você pode dar uma olhada em:

  • Scheme , um antigo Lisp bastante "puro" com um pequeno núcleo
  • Lisp comum, um Lisp maior com um sistema de objetos bem integrado e muitas implementações (é padronizado por ANSI)
  • Raquete de um Lisp digitado
  • Clojure meu favorito, os exemplos acima foram o código Clojure. Um Lisp moderno em execução na JVM. Também existem alguns exemplos de macros Clojure no SO (mas este não é o lugar certo para começar. Eu examinaria os koans 4clojure , braveclojure ou clojure no início).

Ah, a propósito, Lisp significa LISt Processing.

Em relação aos seus exemplos

Vou dar exemplos usando o Clojure abaixo:

Se você pode escrever uma addfunção no Clojure (defn add [a b] ...your-implementation-here... ), pode nomeá-la +assim (defn + [a b] ...your-implementation-here... ). Na verdade, é isso que é feito na implementação real (o corpo da função é um pouco mais envolvido, mas a definição é essencialmente a mesma que escrevi acima).

E a notação infix? Bem, o Clojure usa uma prefixnotação (ou polonesa), para que pudéssemos criar uma infix-to-prefixmacro que transformasse o código prefixado em código Clojure. O que é surpreendentemente fácil (na verdade, é um dos exercícios de macro nos koans do clojure)! Também pode ser visto na natureza, por exemplo, veja a macro Incanter$= .

Aqui está a versão mais simples dos koans explicados:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

Para aprofundar ainda mais, algumas citações do Lisp :

“Parte do que torna o Lisp diferenciado é que ele é projetado para evoluir. Você pode usar o Lisp para definir novos operadores Lisp. À medida que novas abstrações se tornam populares (programação orientada a objetos, por exemplo), sempre é fácil implementá-las no Lisp. Como o DNA, essa linguagem não sai de moda. ”

- Paul Graham, ANSI Common Lisp

“Programar em Lisp é como brincar com as forças primordiais do universo. Parece um raio entre as pontas dos dedos. Nenhuma outra língua parece próxima.

- Glenn Ehrlich, estrada para Lisp

nha
fonte
1
Observe que a metaprogramação, apesar de interessante, não é necessária para apoiar o que o OP está perguntando. Qualquer idioma com suporte para funções de primeira classe é suficiente.
Andrés F.
1
Exemplo da pergunta do OP: (let ((opp # '+)) (print (apply opp' (1 2))))
Kasper van den Berg
1
Nenhuma menção ao Common Lisp?
Coredump 23/03
3
Lembro que em um painel de discussão sobre idiomas, Ken estava falando sobre precedência no APL e concluiu com "Eu quase nunca uso parênteses!" E alguém da platéia gritou: "Isso porque Dick usou todos eles!"
JDługosz 23/03
2
Em uma linguagem no estilo C ++, você poderia reimplementar if, mas seria necessário agrupar os argumentos thene elsecom lambdas. PHP e JavaScript possuem function(), C ++ possui lambdas e existe uma extensão da Apple para C com lambdas.
Damian Yerrick 24/03
9

$test = $number1 $operator1 $number2 $operator2 $number3;

A maioria das implementações de idiomas possui uma etapa em que um analisador analisa seu código e cria uma árvore a partir dele. Por exemplo, a expressão 5 + 5 * 8seria analisada como

  +
 / \
5   *
   / \
  8   8

graças ao conhecimento do compilador sobre precedências. Se você alimentasse variáveis ​​no lugar de operadores, ele não saberia a ordem correta das operações antes de executar o código. Para a maioria das implementações, isso seria um problema sério; portanto, a maioria dos idiomas não permite isso.

É claro que você poderia conceber uma linguagem em que o analisador apenas analise o acima como uma sequência de expressões e operadores, para ser classificado e avaliado em tempo de execução. Presumivelmente, não há muita aplicação para isso.

Muitas linguagens de script permitem a avaliação de expressões arbitrárias (ou pelo menos expressões aritméticas arbitrárias, como no caso de expr) em tempo de execução. Lá, você pode combinar seus números e operadores em uma única expressão e deixar o idioma avaliar isso. No PHP (e muitos outros), essa função é chamada eval.

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

Existem também idiomas que permitem a geração de código em tempo de compilação. A expressão mixin em D vem à minha mente, onde acredito que você poderia escrever algo como

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

Aqui operator1e operator2teria que haver constantes de string conhecidas em tempo de compilação, por exemplo, parâmetros do modelo. number1, number2E number3foram deixados como variáveis de tempo de execução normais.

Outras respostas já discutiram as várias maneiras pelas quais um operador e uma função são mais ou menos a mesma coisa, dependendo do idioma. Mas geralmente há uma diferença sintática entre um símbolo de operador de infixo interno +e um nome de chamada chamado operator1. Deixarei os detalhes para essas outras respostas.

MvG
fonte
+1 Você deve começar com "é possível no PHP com a eval()construção da linguagem " ... Tecnicamente, ele fornece exatamente o resultado desejado solicitado na pergunta.
Armfoot
@ Armfoot: É difícil dizer onde está o foco da pergunta. O título enfatiza o aspecto "variável do tipo operador" e evalnão responde a esse aspecto, pois evalos operadores são apenas strings. Portanto, comecei com uma explicação de por que variáveis ​​do tipo operador causariam problemas antes de começar a discutir alternativas.
MvG 24/03
Entendo seus pontos de vista, mas considero que, ao colocar tudo em uma string, você está basicamente implicando que os tipos de variáveis ​​não são mais relevantes (desde que o PHP foi usado para exemplificar, esse parece ser o foco da pergunta) e, no final, você pode colocá-los da mesma maneira como se algumas dessas variáveis ​​fossem do tipo "operador" e obtivessem o mesmo resultado ... É por isso que acredito que sua sugestão fornece a resposta mais precisa para a pergunta.
Armfoot 24/03
2

Algol 68 tinha exatamente essa característica. Seu exemplo em Algol 68 ficaria assim:

int número1 = 5;                              ¢ (Tipo 'Int') ¢
op operator1 = int ( int a , b ) a + b ; ¢ (Tipo inexistente 'Operador') ¢
prio operator1 = 4;
int número2 = 5;                              ¢ (Tipo 'Int') ¢
op operator2 = int ( int a , b ) a * b ;  ¢(Digite 'Operador' inexistente) ¢
prio operator2 = 5;
int número3 = 8;                              ¢ (Tipo 'Int') ¢

int test = número1 operador1 número2 operador2 número3 ; ¢ 5 + 5 * 8. ¢

var_dump ( teste );

Seu segundo exemplo ficaria assim:

int número4 = 9;
op operador3 = bool ( int a , b ) a < b ; operador
prio3 = 3; se número1 $ operador3 número4, então ¢ 5 <9 (verdadeiro) ¢ imprimir ( verdadeiro ) fi


Você notará que os símbolos do operador são definidos e são atribuídos corpos de método que contêm a operação desejada. Os operadores e seus operandos são todos digitados e os operadores podem ter prioridades atribuídas para que a avaliação ocorra na ordem correta. Você também pode notar que há uma pequena diferença na fonte entre o símbolo do operador e um símbolo variável.

Na verdade, embora o idioma seja escrito usando fontes, as máquinas do dia não podiam lidar com as fontes (fita de papel e cartões perfurados), e o uso de stropping era usado. O programa provavelmente seria inserido como:

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

Você também pode jogar jogos interessantes com a linguagem quando pode definir seus próprios símbolos para operadores, que eu explorei uma vez, há muitos anos ... [2].


Referências:

[1] Introdução informal a Algol 68 por CHLindsey e SG van der Meulen, Holanda do Norte, 1971 .

[2] Algol 68 Phrases, Uma ferramenta para auxiliar na redação de compiladores em Algol 68 , BC Tompsett, Conferência Internacional sobre Aplicações de Algol 68, na Universidade de East Anglia, Norwich, Reino Unido, 1976 ..

Brian Tompsett - 汤 莱恩
fonte