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:
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.
setf
as 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)
.
with-output-to-string
também altera a semântica do código dentro da macro. Da mesma forma, with-current-buffer
e várias outras with-
macros.
dotimes
e similares não podem ser descritos em termos de funções.
ignore-errors
altera a semântica do código que envolve.
Há um monte de macros definidas em cl-lib
pacote, eieio
pacote 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:
Quando eles não introduzem novas construções, com novas semânticas (ou seja, a função faria o trabalho da mesma maneira).
Quando isso é apenas uma espécie de conveniência (ou seja, criar uma macro denominada mvb
que se expande cl-multiple-value-bind
apenas para torná-la mais curta).
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:
Quando conceitos específicos de domínio são obscurecidos por chamadas de função. Por exemplo, se for necessário passar o mesmo context
argumento para várias funções que precisam ser chamadas em sucessão, eu preferiria envolvê-las em uma macro, na qual o context
argumento está oculto.
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)+1
ou 0
ou ((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 f
como uma função, embora a
não seja necessariamente uma função.