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

47

Eu quero dizer 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 isso?

    
por kgongonowdoe 22.03.2016 / 15:48
fonte

4 respostas

103

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 operadores substituindo métodos especiais de sua classe. Em seguida, os operadores padrão (por exemplo, + ) trabalham de acordo com a lógica que você fornece (por exemplo, concatenando cadeias ou adicionando matrizes ou qualquer outra coisa).

Como essas funções definidoras de operadores são apenas métodos, você pode passá-las como se fosse uma função:

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

Outras linguagens permitem que você defina diretamente novos operadores como funções e as use no formato infixo.

-- 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 apoiar seria uma coisa boa. Use uma função simples, é mais legível que $foo $operator $bar .

    
por 22.03.2016 / 16:00
fonte
15

Existem muitos idiomas que permitem algum tipo de metaprogramação . Em particular, estou surpreso em não ver nenhuma resposta sobre a família de idiomas Lisp .

Da wikipedia:

Metaprogramming is the writing of computer programs with the ability to treat programs as their data.

Mais tarde no texto:

Lisp is probably the quintessential language with metaprogramming facilities, both because of its historical precedence and because of the simplicity and power of its metaprogramming.

Idiomas Lisp

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

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

Se representarmos uma lista contendo os elementos a, b, c, d assim: (abcd) obtemos algo que se parece com uma chamada de função Lisp, em que a é a função e b , c , d são os argumentos. Se fato o típico "Olá mundo!" programa poderia ser escrito da seguinte forma: (println "Hello World!")

É claro que b , c ou d podem ser listas que também avaliam algo. O seguinte: (println "I can add :" (+ 1 3) ) imprimiria "" Eu posso adicionar: 4 ".

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

A vantagem do Lisp

Lisps are not so much programming languages as much as a toolkit for making programming languages. A programmable programming language.

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

Por exemplo, em uma linguagem semelhante a C, digamos que você queira escrever um operador if , algo como:

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

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

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

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

Idiomas do mundo real

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

  • Esquema , um velho Lisp bastante "puro" com um pequeno núcleo
  • Common Lisp, um Lisp maior com um sistema de objetos bem integrado e muitas implementações (é padronizado pela ANSI)
  • Racket um Lisp digitado
  • Clojure meu favorito, os exemplos acima eram código de Clojure. Um moderno Lisp rodando na JVM. Existem alguns exemplos de macros Clojure em SO (mas este não é o lugar certo para começar. Eu gostaria de ver 4clojure , braveclojure ou koans clojure no início)).

Ah, e a propósito, Lisp significa Processamento de LISt.

Em relação aos seus exemplos

Vou dar exemplos usando o Clojure abaixo:

Se você pode escrever uma função add em Clojure (defn add [a b] ...your-implementation-here... ) , você pode nomear + como (defn + [a b] ...your-implementation-here... ) . Isto é, de fato, o que é feito no real implementação (o corpo da função é um pouco mais envolvido, mas a definição é essencialmente a mesma que escrevi acima).

E quanto à notação infixada? Bem, o Clojure usa uma notação prefix (ou polonês), portanto, poderíamos criar uma macro infix-to-prefix que transformaria o código prefixado em código Clojure. O que na verdade é surpreendentemente fácil (na verdade, é um dos exercícios de macro nos koans clojure)! Também pode ser visto na natureza, por exemplo, consulte Incanter $= macro .

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 impulsionar ainda mais o ponto, algumas citações de Lisp :

“Part of what makes Lisp distinctive is that it is designed to evolve. You can use Lisp to define new Lisp operators. As new abstractions become popular (object-oriented programming, for example), it always turns out to be easy to implement them in Lisp. Like DNA, such a language does not go out of style.”

— Paul Graham, ANSI Common Lisp

“Programming in Lisp is like playing with the primordial forces of the universe. It feels like lightning between your fingertips. No other language even feels close.”

— Glenn Ehrlich, Road to Lisp

    
por 23.03.2016 / 11:08
fonte
9

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

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

  +
 / \
5   *
   / \
  8   8

graças ao conhecimento do compilador sobre precedências. Se você alimentasse as variáveis no lugar dos operadores, 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, então a maioria das linguagens não permite isso.

Você poderia, é claro, conceber uma linguagem em que o analisador apenas analisa o acima como uma seqüê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 arbitrária expressões aritméticas como no caso de expr ) no tempo de execução. Lá você poderia simplesmente combinar seus números e operadores em uma única expressão e deixar a linguagem avaliar isso. No PHP (e muitos outros) essa função é chamada eval .

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

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

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

Aqui operator1 e operator2 teriam que ser constantes de string que são conhecidas em tempo de compilação, por ex. parâmetros do modelo. number1 , number2 e number3 foram deixados como variáveis de tempo de execução normais.

Outras respostas já discutidas as várias maneiras como um operador e uma função são mais ou menos a mesma coisa, dependendo do idioma. Mas normalmente há uma diferença sintática entre um símbolo de operador infix embutido como + e um nome chamado como operator1 . Vou deixar os detalhes para as outras respostas.

    
por 23.03.2016 / 14:04
fonte
2

Algol 68 tinha exatamente esse recurso. Seu exemplo em Algol 68 ficaria assim:

int number1 = 5;                              ¢ (Type 'Int') ¢
op operator1 = int (int a,b) a + b; ¢ (Type non-existent 'Operator') ¢
prio operator1 = 4;
int number2 = 5;                              ¢ (Type 'Int') ¢
op operator2 = int (int a,b) a * b;  ¢ (Type non-existent 'Operator') ¢
prio operator2 = 5;
int number3 = 8;                              ¢ (Type 'Int') ¢

int test = number1 operator1 number2 operator2 number3; ¢ 5 + 5 * 8. ¢

var_dump(test);

Seu segundo exemplo seria assim:

int number4 = 9;
op operator3 = bool (int a,b) a < b;
prio operator3 = 3;
if number1 $operator3 number4 then ¢ 5 < 9 (true) ¢
print(true)
fi

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

Na verdade, embora a linguagem seja escrita usando fonte, as máquinas do dia não poderiam manipular as fontes (fita de papel e cartões perfurados) e stropping foi 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 o idioma quando você pode definir seus próprios símbolos para os operadores, que eu explorei uma vez, muitos anos atrás ... [2].

Referências:

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

[2] Algol 68 Frases, Uma ferramenta para auxiliar a escrita de compiladores em Algol 68 , B.C. Tompsett, Conferência Internacional sobre as Aplicações de Algol 68, na Universidade de East Anglia, Norwich, Reino Unido, 1976. .

    
por 24.03.2016 / 16:20
fonte