Por que as macros não estão incluídas nas linguagens de programação mais modernas?

31

Eu sei que eles são implementados extremamente inseguros em C / C ++. Eles não podem ser implementados de maneira mais segura? As desvantagens das macros são realmente ruins o suficiente para compensar o enorme poder que elas fornecem?

Casebash
fonte
4
Exatamente qual o poder que as macros fornecem que não podem ser facilmente alcançadas de outra maneira?
Chinmay Kanchi 5/09/10
2
Em C #, foi necessária uma extensão de idioma principal para envolver a criação de uma propriedade simples e o campo de backup em uma declaração. E isso requer mais do que macros lexicais: como você pode criar um recurso correspondente ao WithEventsmodificador do Visual Basic ? Você precisaria de algo como macros semânticas.
Jeffrey Hantin
3
O problema das macros é que, como todos os mecanismos poderosos, ele permite que um programador quebre as suposições que outras pessoas fizeram ou farão. Pressupostos são a chave para o raciocínio e sem a capacidade de raciocinar de maneira viável sobre a lógica, fazer o progresso se tornar proibitivo.
21912 dan_waterworth
2
@ China: macros geram código. Não há recurso na linguagem Java com esse poder.
kevin Cline
1
@Chinmay Kanchi: Macros permitem executar (avaliar) código em tempo de compilação, em vez de em tempo de execução.
Giorgio

Respostas:

57

Eu acho que a principal razão é que as macros são lexicais . Isso tem várias consequências:

  • O compilador não tem como verificar se uma macro está semanticamente fechada, ou seja, que representa uma "unidade de significado" como uma função. (Considere #define TWO 1+1- o que é TWO*TWOigual? 3.)

  • Macros não são digitadas como funções. O compilador não pode verificar se os parâmetros e o tipo de retorno fazem sentido. Ele só pode verificar a expressão expandida que usa a macro.

  • Se o código não compilar, o compilador não tem como saber se o erro está na própria macro ou no local em que a macro é usada. O compilador reportará o local errado na metade do tempo ou precisará reportar os dois, mesmo que um deles esteja provavelmente correto. (Considere #define min(x,y) (((x)<(y))?(x):(y)): O que o compilador deve fazer se os tipos de xe ynão corresponderem ou não forem implementados operator<?)

  • As ferramentas automatizadas não podem trabalhar com elas de maneiras semanticamente úteis. Em particular, você não pode ter coisas como o IntelliSense para macros que funcionam como funções, mas se expandem para uma expressão. (Novamente, o minexemplo.)

  • Os efeitos colaterais de uma macro não são tão explícitos quanto com as funções, causando confusão potencial para o programador. (Considere novamente o minexemplo: em uma chamada de função, você sabe que a expressão para xé avaliada apenas uma vez, mas aqui você não pode saber sem olhar para a macro.)

Como eu disse, essas são todas as consequências do fato de as macros serem lexicais. Quando você tenta transformá-los em algo mais adequado, você acaba com funções e constantes.

Timwi
fonte
32
Nem todas as linguagens de macro são lexicais. Por exemplo, as macros do esquema são sintáticas e os modelos C ++ são semânticos. Os macro sistemas lexicais têm a distinção de que podem ser inseridos em qualquer idioma sem conhecer sua sintaxe.
Jeffrey Hantin
8
@ Jeffrey: Eu acho que minhas “macros são lexicais” é realmente uma abreviação de “quando as pessoas se referem a macros em uma linguagem de programação, geralmente pensam em macros lexicais”. É lamentável que Scheme usasse esse termo carregado para algo que é fundamentalmente diferente. Os modelos C ++, no entanto, não são amplamente referidos como macros, presumivelmente precisamente porque não são inteiramente lexicais.
Timwi
25
Penso que o uso do termo macro por Scheme remonta ao Lisp, o que provavelmente significa que antecede a maioria dos outros usos. IIRC, o sistema C / C ++ foi originalmente chamado de pré - processador .
Bevan
16
@Bevan está certo. Dizer que macros são lexicais é como dizer que os pássaros não podem voar porque o pássaro com o qual você está mais familiarizado é um pinguim. Dito isto, a maioria (mas não todos) dos pontos que você levanta também se aplica a macros sintáticas, embora talvez em menor grau.
Laurence Gonsalves
13

Mas sim, as macros podem ser projetadas e implementadas melhor do que em C / C ++.

O problema com as macros é que elas são efetivamente um mecanismo de extensão de sintaxe de linguagem que reescreve seu código em outra coisa.

  • No caso de C / C ++, não há verificação de sanidade fundamental. Se você for cuidadoso, tudo está bem. Se você cometer um erro, ou se usar demais macros, poderá encontrar grandes problemas.

    Adicione a isso que muitas coisas simples que você pode fazer com macros (estilo C / C ++) podem ser feitas de outras maneiras em outros idiomas.

  • Em outros idiomas, como vários dialetos Lisp, as macros são mais bem integradas à sintaxe do idioma principal, mas você ainda pode ter problemas com as declarações em uma macro "vazando". Isso é tratado por macros higiênicas .


Breve contexto histórico

As macros (abreviação de instruções macro) apareceram pela primeira vez no contexto da linguagem assembly. Segundo a Wikipedia , as macros estavam disponíveis em alguns montadores da IBM na década de 1950.

O LISP original não tinha macros, mas foi introduzido pela primeira vez no MacLisp em meados da década de 1960: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformation-aparecem .http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . Antes disso, "fexprs" fornecia funcionalidade semelhante a macro.

As versões mais antigas do C não tinham macros ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Estes foram adicionados por volta de 1972-73 através de um pré-processador. Antes disso, C apenas suportava #includee#define .

O macro pré-processador M4 teve origem em 1977.

Linguagens mais recentes aparentemente implementam macros em que o modelo de operação é sintático e não textual.

Portanto, quando alguém fala sobre a primazia de uma definição específica do termo "macro", é importante observar que o significado evoluiu ao longo do tempo.

Stephen C
fonte
9
C ++ é abençoado (?) Com DOIS sistemas macro: o pré-processador e a expansão do modelo.
Jeffrey Hantin
Eu acho que alegar que a expansão do modelo é uma macro está ampliando a definição de macro. Usar sua definição torna a pergunta original sem sentido e completamente errada, pois a maioria das linguagens modernas possui macros (de acordo com sua definição). No entanto, o uso da macro como a maioria esmagadora dos desenvolvedores a utilizará faz dela uma boa pergunta.
Dunk
@ Dunk, a definição de uma macro como no Lisp é anterior à compreensão burra "moderna" como no patético pré-processador C.
SK-lógica
@ SK: É melhor estar "tecnicamente" correto e ofuscar a conversa ou é melhor ser entendido?
Dunk
1
@ Dunker, a terminologia não é de propriedade das hordas ignorantes. Sua ignorância nunca deve ser levada em consideração, caso contrário, levará a emburrecer todo o domínio da ciência da computação. Se alguém entende "macro" apenas como uma referência a um pré-processador C, não passa de uma ignorância. Duvido que haja uma proporção significativa de pessoas que nunca ouviram falar de Lisp ou mesmo de, pardonnez mon français, "macros" do VBA no Office.
21413 SK-logic
12

Macros podem, como Scott observa , permitir ocultar a lógica. Obviamente, o mesmo acontece com funções, classes, bibliotecas e muitos outros dispositivos comuns.

Mas um poderoso sistema de macros pode ir além, permitindo que você projete e utilize sintaxe e estruturas que normalmente não são encontradas no idioma. Essa pode ser uma ferramenta maravilhosa: idiomas específicos de domínio, geradores de código e muito mais, tudo dentro do conforto de um ambiente de idioma único ...

No entanto, pode ser abusado. Isso pode dificultar a leitura, a compreensão e a depuração do código, aumentar o tempo necessário para os novos programadores se familiarizarem com uma base de código e levar a erros e atrasos dispendiosos.

Portanto, para linguagens destinadas a simplificar a programação (como Java ou Python), esse sistema é um anátema.

Shog9
fonte
3
E então Java saiu dos trilhos, adicionando foreach, anotações, afirmações, genéricos-by-apagamento, ...
Jeffrey Hantin
2
@ Jeffrey: e não vamos esquecer, muitos geradores de código de terceiros . Pensando bem, vamos esquecer isso.
Shog9
4
Outro exemplo de como o Java era simplista em vez de simples.
Jeffrey Hantin
3
@JeffreyHantin: O que há de errado com foreach?
Casebash 11/04
1
@ Dunk Essa analogia pode ser um exagero ... mas acho que a extensibilidade da linguagem é como o plutônio. Caro para implementar, muito difícil de usinar nas formas desejadas e notavelmente perigoso se mal utilizado, mas também incrivelmente poderoso quando aplicado bem.
Jeffrey Hantin
6

As macros podem ser implementadas com muita segurança em algumas circunstâncias - no Lisp, por exemplo, macros são apenas funções que retornam código transformado como uma estrutura de dados (expressão s). Obviamente, o Lisp se beneficia significativamente do fato de ser homoicônico e do fato de que "código são dados".

Um exemplo de como as macros podem ser fáceis é este exemplo do Clojure que especifica um valor padrão a ser usado no caso de uma exceção:

(defmacro on-error [default-value code]
  `(try ~code (catch Exception ~'e ~default-value)))

(on-error 0 (+ nil nil))               ;; would normally throw NullPointerException
=> 0                                   ;l; but we get the default value

Mesmo em Lisps, o conselho geral é "não use macros, a menos que você precise".

Se você não estiver usando uma linguagem homoicônica, as macros ficarão muito mais complicadas e as várias outras opções terão algumas armadilhas:

  • Macros baseadas em texto - por exemplo, o pré-processador C - simples de implementar, mas muito complicado de usar corretamente, pois você precisa gerar a sintaxe de origem correta na forma de texto, incluindo quaisquer peculiaridades sintáticas
  • DSLS baseado em macro - por exemplo, o sistema de modelos C ++. O complexo, por si só, pode resultar em alguma sintaxe complicada, pode ser extremamente complexo para o manuseamento correto dos escritores de compiladores e ferramentas, pois introduz uma nova e significativa complexidade na sintaxe e na semântica da linguagem.
  • APIs de manipulação de AST / bytecode - por exemplo, reflexão Java / geração de bytecode - teoricamente muito flexíveis, mas podem ficar muito detalhadas: pode ser necessário muito código para fazer coisas bem simples. Se são necessárias dez linhas de código para gerar o equivalente a uma função de três linhas, você não ganhou muito com seus esforços de metaprogramação ...

Além disso, tudo o que uma macro pode fazer pode ser conseguido de alguma outra maneira em uma linguagem completa (mesmo que isso signifique escrever muitos clichês). Como resultado de todo esse truque, não é de surpreender que muitas linguagens decidam que as macros não valem realmente todo o esforço para implementar.

Mikera
fonte
Ugh. Não há mais lixo "código é dados é uma coisa boa". Código é código, dados são dados, e não separar adequadamente os dois é uma falha de segurança. Você pensaria que o surgimento da injeção de SQL como uma das maiores classes de vulnerabilidade existentes desacreditaria a idéia de tornar códigos e dados facilmente intercambiáveis ​​de uma vez por todas.
Mason Wheeler
10
@Mason - Eu não acho que você entenda o conceito de código é dados. Todo o código-fonte do programa C também é um dado - por acaso é expresso em formato de texto. Lisps são os mesmos, exceto que expressam o código em estruturas de dados intermediárias práticas (expressões-s) que permitem que ele seja manipulado e transformado por macros antes da compilação. Em ambos os casos, enviar entrada não confiável ao compilador pode ser uma falha de segurança - mas isso é difícil de fazer acidentalmente e seria sua culpa fazer algo estúpido, não o compilador.
Mikera # 11/13
A ferrugem é outro sistema macro seguro interessante. Possui regras / semânticas estritas para evitar os tipos de problemas associados às macros lexicais C / C ++ e usa uma DSL para capturar partes da expressão de entrada em variáveis ​​de macro a serem inseridas posteriormente. Não é tão poderoso quanto a capacidade do Lisps de chamar qualquer função na entrada, mas fornece um grande conjunto de macros de manipulação úteis que podem ser chamadas de outras macros.
zstewart
Ugh. Não há mais lixo de segurança . Caso contrário, culpe todos os sistemas com a arquitetura von Neumann integrada, por exemplo, todos os processadores IA-32 com capacidade de código auto-modificável. Eu suspeito que você possa preencher o buraco fisicamente ... De qualquer forma, você deve enfrentar o fato em geral: o isomorfismo entre código e dados é a natureza do mundo de várias maneiras . E é (provavelmente) você, o programador, responsável por manter a invariância dos requisitos de segurança, o que não é o mesmo que aplicar artificialmente segregação prematura em qualquer lugar.
FrankHB
4

Para responder às suas perguntas, pense sobre o uso predominante das macros (Aviso: código compilado pelo cérebro).

  • Macros usadas para definir constantes simbólicas #define X 100

Isso pode ser facilmente substituído por: const int X = 100;

  • Macros usadas para definir (essencialmente) funções agnósticas de tipo em linha #define max(X,Y) (X>Y?X:Y)

Em qualquer idioma que ofereça suporte à sobrecarga de funções, isso pode ser emulado de uma maneira muito mais segura, tendo funções sobrecarregadas do tipo correto ou, em um idioma que suporte genéricos, por uma função genérica. A macro tentará com prazer comparar qualquer coisa, incluindo ponteiros ou seqüências de caracteres, que podem ser compiladas, mas quase certamente não é o que você queria. Por outro lado, se você tornou as macros seguras para o tipo, elas não oferecem benefícios ou conveniência em relação às funções sobrecarregadas.

  • Macros usadas para especificar atalhos para elementos usados ​​com frequência. #define p printf

Isso é facilmente substituído por uma função p()que faz a mesma coisa. Isso está bastante envolvido em C (exigindo o uso da va_arg()família de funções), mas em muitas outras linguagens que suportam números variáveis ​​de argumentos de funções, é muito mais simples.

O suporte a esses recursos em um idioma e não em um idioma macro especial é mais simples, menos propenso a erros e muito menos confuso para outras pessoas que leem o código. Na verdade, não consigo pensar em um único caso de uso para macros que não possam ser facilmente duplicadas de outra maneira. O único lugar em que as macros são realmente úteis é quando elas estão vinculadas a construções de compilação condicional como #if(etc.).

Nesse ponto, não discutirei com você, pois acredito que soluções sem pré-processador para compilação condicional em linguagens populares são extremamente complicadas (como a injeção de bytecode em Java). Mas linguagens como D criaram soluções que não exigem um pré-processador e não são mais complicadas do que usar condicionais do pré-processador, além de serem muito menos propensas a erros.

Chinmay Kanchi
fonte
1
se você precisar # definir o máximo, coloque colchetes nos parâmetros, para que não haja efeitos inesperados da precedência do operador ... como # definir o máximo (X, Y) ((X)> (Y)? (X) :( Y))
foo
Você percebe que era apenas um exemplo ... A intenção era ilustrar.
perfil completo de Chinmay Kanchi
+1 para este tratamento sistemático do assunto. Eu gosto de acrescentar que a compilação condicional pode facilmente se tornar um pesadelo - lembro que havia um programa chamado "unifdef" (??) cujo objetivo era tornar visível o que restava após o pós-processamento.
Ingo
7
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way: em C, pelo menos, você não pode formar identificadores (nomes de variáveis) usando concatenação de token sem usar macros.
Charles Salvia
As macros possibilitam garantir que certas estruturas de código e dados permaneçam "paralelas". Por exemplo, se houver um pequeno número de condições com mensagens associadas e for necessário persistir em um formato conciso, tentar usar um enumpara definir as condições e uma matriz de cadeias constantes para definir as mensagens podem resultar em problemas se o enum e a matriz ficam fora de sincronia. Usar uma macro para definir todos os pares (enum, string) e incluir essa macro duas vezes com outras definições apropriadas no escopo a cada vez permite que cada um coloque cada valor de enum ao lado de sua string.
Supercat
2

O maior problema que já vi nas macros é que, quando muito usadas, elas tornam o código muito difícil de ler e manter, pois permitem ocultar a lógica na macro que pode ou não ser fácil de encontrar (e pode ou não ser trivial )

Scott Dorman
fonte
A macro não deve ser documentada?
Casebash 5/09/10
@ Casebash: Naturalmente, a macro deve ser documentada como qualquer outro código-fonte ... mas, na prática, eu raramente a vejo fazer isso.
Scott Dorman
3
Já depurar documentação? Não basta apenas lidar com códigos mal escritos.
JeffO 17/09/10
OOP também tem alguns desses problemas ...
aoeu256 22/07
2

Antes de começar, observe que os MACROs em C / C ++ são muito limitados, propensos a erros e não são realmente úteis.

Os MACROs implementados na linguagem assembler LISP ou z / OS, por exemplo, podem ser confiáveis ​​e incrivelmente úteis.

Mas, devido ao abuso da funcionalidade limitada em C, eles têm uma má reputação. Portanto, ninguém mais implementa macros; em vez disso, você obtém coisas como modelos, que fazem algumas das coisas simples que as macros costumavam fazer e coisas como anotações de Java, que fazem algumas das coisas mais complexas que as macro costumavam fazer.

James Anderson
fonte