ti-enxame.com

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

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?

33
Casebash

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 faz uma função. (Considere #define TWO 1+1 - o que significa TWO*TWO? 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 for compilado, 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 x e y não corresponderem ou não implementarem 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 exemplo min).

  • 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 exemplo min: 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.

58
Timwi

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 da 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 é abordado 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. De acordo com Wikipedia , as macros estavam disponíveis em alguns montadores da IBM nos anos 50.

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 -definido-código-transformação-aparece . 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 suportava apenas #include e #define.

O macro pré-processador M4 teve origem em cerca de 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 o primado de uma definição específica do termo "macro", é importante observar que o significado evoluiu ao longo do tempo.

13
Stephen C

As macros podem, como observa Scott , permitir ocultar a lógica. Obviamente, funções, classes, bibliotecas e muitos outros dispositivos comuns também.

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.

12
Shog9

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 - p. 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 - p. 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 complexidade significativa na sintaxe e na semântica da linguagem.
  • APIs de manipulação de AST/bytecode - p. Java reflexão/geração de bytecode - teoricamente muito flexível, mas pode ficar muito detalhada: pode ser necessário muito código para fazer coisas bem simples. Se forem necessárias dez linhas de código para gerar o equivalente a um função de três linhas, então 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 surpreende que muitas linguagens decidam que as macros não valem realmente todo o esforço para implementar.

6
mikera

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ção, 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á comparar qualquer coisa incluindo ponteiros ou seqüências de caracteres, que podem ser compilados, 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 que você use a família de funções va_arg()]), mas em muitos outros idiomas que suportam números variáveis ​​de argumentos de funções, é muito mais simples.

O suporte a esses recursos dentro um idioma em vez de 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 caso de uso único para macros que não possam ser facilmente duplicadas de outra maneira. O local somente onde 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.

5
Chinmay Kanchi

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

Os MACROs implementados na linguagem LISP ou assembler do z/OS 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.

2
James Anderson

O maior problema que eu já vi com 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 ).

2
Scott Dorman