Quando usar macro ou não usar [fechado]


12

Quando devo usar macro no meu programa ou não?

Esta pergunta é inspirada em uma resposta informativa de @tarsius. Eu acredito que muitos têm ótimas experiências para compartilhar outros. Espero que essa pergunta se torne uma das grandes questões subjetivas e ajude os programadores do Emacs.

Respostas:


13

Regra prática

Use macros para sintaxe , mas nunca para semântica.

É bom usar uma macro para um pouco de açúcar sintático, mas é uma má idéia colocar a lógica em um corpo de macro, independentemente de estar na própria macro ou na expansão. Se você sentir a necessidade de fazer isso, escreva uma função e uma "macro complementar" para o açúcar sintático. Simplesmente falando, se você tem uma letmacro, está procurando problemas.

Por quê?

As macros têm muitas desvantagens:

  • Eles se expandem no momento da compilação e, portanto, devem ser escritos com cuidado para manter a compatibilidade binária entre várias versões. Por exemplo, remover uma função ou variável usada na expansão de uma macro interromperá o código de bytes de todos os pacotes dependentes que estavam usando essa macro. Em outras palavras, se você alterar a implementação de uma macro de maneira incompatível, todos os pacotes dependentes deverão ser recompilados. E a compatibilidade binária nem sempre é óbvia…
  • Você deve manter uma ordem de avaliação intuitiva . É fácil escrever uma macro que avalie um formulário com muita frequência, ou na hora errada, ou no lugar errado, etc.
  • Você deve observar a semântica da ligação : as macros herdam seu contexto de ligação do site de expansão , o que significa que você não sabe a priori quais variáveis ​​lexicais existem e se a ligação lexical está disponível. Uma macro deve funcionar com ambas as semânticas de ligação.
  • Relacionado a isso, você deve ter cuidado ao escrever macros que criam fechamentos . Lembre-se, você obtém as ligações do site de expansão, não da definição lexical; portanto, observe o que você encerra e como você cria o fechamento (lembre-se, você não sabe se a ligação lexical está ativa no site de expansão e se você ainda tem fechamentos disponíveis).

1
As notas sobre ligação lexical versus expansão macro são particularmente interessantes; graças lunaryorn. (Eu nunca permitiu lexical de ligação para qualquer código que eu escrevi, por isso não é um conflito que eu já tinha pensado.)
phils

6

Da minha experiência lendo, depurando e modificando o código Elisp meu e de outros, sugiro evitar o máximo possível de macros.

O óbvio sobre as macros é que elas não avaliam seus argumentos. O que significa que se você tiver uma expressão complexa envolvida em uma macro, não tem idéia se ela será avaliada ou não e em qual contexto, a menos que procure a definição da macro. Isso pode não ser muito importante para você, já que você conhece a definição de sua própria macro (atualmente, provavelmente não alguns anos depois).

Essa desvantagem não se aplica às funções: todos os argumentos são avaliados antes da chamada da função. O que significa que você pode criar a complexidade de avaliação em uma situação de depuração desconhecida. Você pode até pular as definições das funções desconhecidas para você, desde que elas retornem dados diretos e você presuma que elas não tocam no estado global.

Para reformulá-lo de outra maneira, as macros se tornam parte do idioma. Se você está lendo um código com macros desconhecidas, está quase lendo um código em um idioma que não conhece.

Exceções às reservas acima:

As macros padrão, como push, pop, dolistfazer realmente muito para você, e são conhecidos por outros programadores. Eles são basicamente parte das especificações de idioma. Mas é praticamente impossível padronizar sua própria macro. Não é apenas difícil criar algo simples e útil como os exemplos acima, mas ainda mais difícil é convencer todos os programadores a aprender.

Além disso, não me importo com macros como save-selected-window: eles armazenam e restauram o estado e avaliam seus argumentos da mesma maneira que prognfaz. Bem simples, na verdade torna o código mais simples.

Outro exemplo de macros OK pode ser algo como defhydraou use-package. Essas macros vêm basicamente com seu próprio mini-idioma. E eles ainda tratam dados simples ou agem assim progn. Além disso, eles geralmente estão no nível superior, portanto, não há variáveis ​​na pilha de chamadas para os argumentos da macro dependerem, tornando-o um pouco mais simples.

Um exemplo de macro ruim, na minha opinião, é magit-section-actione magit-section-caseno Magit. O primeiro já foi removido, mas o segundo ainda permanece.


4

Vou ser franco: não entendo o que "usar para sintaxe, não para semântica" significa. É por isso que parte dessa resposta lida com a resposta da lunaryon e a outra parte será minha tentativa de responder à pergunta original.

Quando a palavra "semântica" usada na programação, refere-se à maneira como o autor da linguagem escolheu interpretar sua linguagem. Isso geralmente se resume a um conjunto de fórmulas que transformam as regras gramaticais da linguagem em outras regras com as quais se supõe que o leitor esteja familiarizado. Por exemplo, Plotkin, em seu livro Structural Approach to Operational Semantics, usa linguagem de derivação lógica para explicar o que avalia a sintaxe abstrata (o que significa executar um programa). Em outras palavras, se a sintaxe é o que constitui a linguagem de programação em algum nível, ela precisa ter alguma semântica, caso contrário, é ... bem, não uma linguagem de programação.

Mas, por enquanto, vamos esquecer as definições formais, afinal, é importante entender a intenção. Portanto, parece que o lunaryon incentivaria seus leitores a usar macros quando nenhum novo significado fosse adicionado ao programa, mas apenas algum tipo de abreviação. Para mim, isso parece estranho e em forte contraste com o modo como as macros são realmente usadas. Abaixo estão exemplos que criam claramente novos significados:

  1. defun e amigos, que são uma parte essencial do idioma, não podem ser descritos nos mesmos termos que você descreveria as chamadas de função.

  2. setfas regras que descrevem como as funções funcionam são inadequadas para descrever os efeitos de uma expressão como (setf (aref x y) z).

  3. with-output-to-stringtambém altera a semântica do código dentro da macro. Da mesma forma, with-current-buffere várias outras with-macros.

  4. dotimes e similares não podem ser descritos em termos de funções.

  5. ignore-errors altera a semântica do código que envolve.

  6. Há um monte de macros definidas em cl-libpacote, eieiopacote e algumas outras bibliotecas quase onipresentes usadas no Emacs Lisp que, embora pareçam formas de função, têm interpretações diferentes.

Portanto, as macros são a ferramenta para introduzir novas semânticas em uma linguagem. Não consigo pensar em uma maneira alternativa de fazer isso (pelo menos não no Emacs Lisp).


Quando eu não usaria macros:

  1. Quando eles não introduzem novas construções, com novas semânticas (ou seja, a função faria o trabalho da mesma maneira).

  2. Quando isso é apenas uma espécie de conveniência (ou seja, criar uma macro denominada mvbque se expande cl-multiple-value-bindapenas para torná-la mais curta).

  3. Quando espero que muitos códigos de manipulação de erros sejam ocultados pela macro (como foi observado, as macros são difíceis de depurar).


Quando eu preferiria fortemente macros sobre funções:

  1. Quando conceitos específicos de domínio são obscurecidos por chamadas de função. Por exemplo, se for necessário passar o mesmo contextargumento para várias funções que precisam ser chamadas em sucessão, eu preferiria envolvê-las em uma macro, na qual o contextargumento está oculto.

  2. Quando o código genérico em forma de função é excessivamente detalhado (as construções de iteração simples no Emacs Lisp são muito detalhadas para o meu gosto e desnecessariamente expõem o programador aos detalhes de implementação de baixo nível).


Ilustração

Abaixo está a versão diluída, destinada a dar uma intuição sobre o que se entende por semântica em ciência da computação.

Suponha que você tenha uma linguagem de programação extremamente simples, com apenas estas regras de sintaxe:

variable := 1 | 0
operation := +
expression := variable | (expression operation expression)

com esta linguagem, podemos construir "programas" como (1+0)+1ou 0ou ((0+0))etc.

Mas enquanto não fornecemos regras semânticas, esses "programas" não têm sentido.

Suponha que agora equiparmos nossa linguagem com as regras (semânticas):

0+0  0+1  1+0  1+1  (exp)
---, ---, ---, ---, -----
 0   1+0   1    0    exp

Agora podemos calcular usando essa linguagem. Ou seja, podemos pegar a representação sintática, entendê-la abstratamente (em outras palavras, convertê-la em sintaxe abstrata) e depois usar regras semânticas para manipular essa representação.

Quando falamos sobre a família de linguagens Lisp, as regras semânticas básicas da avaliação de funções são aproximadamente as do cálculo lambda:

(f x)  (lambda x y)   x
-----, ------------, ---
 fx        ^x.y       x

Os mecanismos de cotação e macroexpansão também são conhecidos como ferramentas de metaprogramação, ou seja, permitem falar sobre o programa, em vez de apenas programar. Eles conseguem isso criando novas semânticas usando a "camada meta". O exemplo de uma macro tão simples é:

(a . b)
-------
(cons a b)

Esta não é realmente uma macro no Emacs Lisp, mas poderia ter sido. Eu escolhi apenas por uma questão de simplicidade. Observe que nenhuma das regras semânticas definidas acima se aplicaria a esse caso, pois o candidato mais próximo (f x)interpreta fcomo uma função, embora anão seja necessariamente uma função.


1
"Açúcar sintático" não é realmente um termo em ciência da computação. É algo usado pelos engenheiros para significar várias coisas. Então, eu não tenho uma resposta definitiva. Como estamos falando de semântica, a regra para interpretar a sintaxe do formulário: (x y)é aproximadamente "avalie y, então chame x com o resultado da avaliação anterior". Quando você escreve (defun x y), y não é avaliado e nem é x. Se você pode escrever um código com efeito equivalente usando diferentes semânticas não nega que esse pedaço de código tenha semântica própria.
wvxvw

1
Re 'seu segundo comentário: Você pode me referir à definição de macro que você está usando, que diz o que afirma? Acabei de dar um exemplo em que uma nova regra semântica é introduzida por meio de uma macro. Pelo menos no sentido preciso de CS. Não acho que argumentar sobre definições seja produtivo, e também acho que as definições usadas no CS são a melhor correspondência para esta discussão.
wvxvw

1
Bem ... você tem sua própria idéia do que significa a palavra "semântica", o que é meio irônico, se você pensar sobre isso :) Mas, na verdade, essas coisas têm definições muito precisas, você apenas, bem .. ignore aqueles. O livro que vinculei na resposta é um livro padrão sobre semântica, conforme ensinado no curso correspondente em CS. Se você acabou de ler o artigo correspondente da Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29 , verá o que realmente é. O exemplo é a regra para interpretar (defun x y)vs aplicativo de função.
wvxvw

Oh, bem, eu não acho que essa seja a minha maneira de discutir. Foi um erro até tentar iniciar um; Eu removi meus comentários porque não faz sentido em tudo isso. Sinto muito por ter perdido seu tempo.
Lunaryorn 17/03/16
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.