Lendo os ensaios de Paul Graham sobre linguagens de programação, poderíamos pensar que as macros Lisp são o único caminho a percorrer. Como desenvolvedor ocupado, trabalhando em outras plataformas, não tive o privilégio de usar as macros do Lisp. Como alguém que quer entender o burburinho, explique o que torna esse recurso tão poderoso.
Por favor, relacione isso com algo que eu entenderia do mundo do desenvolvimento em Python, Java, C # ou C.
macros
lisp
homoiconicity
mentiroso
fonte
fonte
Respostas:
Para dar uma resposta curta, as macros são usadas para definir extensões de sintaxe de idioma para Common Lisp ou Linguagens Específicas de Domínio (DSLs). Essas linguagens são incorporadas diretamente no código Lisp existente. Agora, as DSLs podem ter sintaxe semelhante ao Lisp (como o Prolog Interpreter para Common Lisp de Peter Norvig ) ou completamente diferente (por exemplo, Infix Notation Math for Clojure).
Aqui está um exemplo mais concreto:
Python possui compreensão de lista incorporada à linguagem. Isso fornece uma sintaxe simples para um caso comum. A linha
gera uma lista contendo todos os números pares entre 0 e 9. De volta aos 1,5 dias do Python, não havia essa sintaxe; você usaria algo mais como este:
Ambos são funcionalmente equivalentes. Vamos invocar nossa suspensão de descrença e fingir que o Lisp tem uma macro de loop muito limitada que apenas faz iteração e não é uma maneira fácil de fazer o equivalente à compreensão da lista.
No Lisp, você pode escrever o seguinte. Devo observar que este exemplo artificial é escolhido para ser idêntico ao código Python, não um bom exemplo de código Lisp.
Antes de ir mais longe, devo explicar melhor o que é uma macro. É uma transformação realizada em código por código. Ou seja, um pedaço de código, lido pelo intérprete (ou compilador), que recebe o código como argumento, manipula e retorna o resultado, que é executado no local.
É claro que muitas digitações e programadores são preguiçosos. Assim, poderíamos definir o DSL para fazer a compreensão da lista. De fato, já estamos usando uma macro (a macro de loop).
Lisp define algumas formas especiais de sintaxe. A citação (
'
) indica que o próximo token é literal. O quasiquote ou backtick (`
) indica que o próximo token é um literal com escapes. Escapes são indicados pelo operador de vírgula. O literal'(1 2 3)
é o equivalente ao Python[1, 2, 3]
. Você pode atribuí-lo a outra variável ou usá-lo no local. Você pode pensar`(1 2 ,x)
como o equivalente do Python,[1, 2, x]
ondex
é uma variável definida anteriormente. Essa notação de lista faz parte da mágica que entra nas macros. A segunda parte é o leitor Lisp, que substitui inteligentemente as macros por código, mas que é melhor ilustrado abaixo:Assim, podemos definir uma macro chamada
lcomp
(abreviação de compreensão de lista). Sua sintaxe será exatamente como o python que usamos no exemplo[x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
Agora podemos executar na linha de comando:
Bem arrumado, não é? Agora não para por aí. Você tem um mecanismo ou um pincel, se quiser. Você pode ter qualquer sintaxe que desejar. Como Python ou
with
sintaxe do C # . Ou sintaxe LINQ do .NET. No final, é isso que atrai as pessoas para o Lisp - a máxima flexibilidade.fonte
(loop for x from 0 below 10 when (evenp x) collect x)
, mais exemplos aqui . Mas, de fato, laço é "apenas uma macro" (I, na verdade re-implementado a partir do zero, há algum tempo )Você encontrará um debate abrangente sobre a macro lisp aqui .
Um subconjunto interessante desse artigo:
fonte
As macros Lisp comuns estendem essencialmente as "primitivas sintáticas" do seu código.
Por exemplo, em C, a construção switch / case funciona apenas com tipos integrais e, se você deseja usá-la para floats ou strings, fica com instruções if aninhadas e comparações explícitas. Também não há como você escrever uma macro C para fazer o trabalho por você.
Mas, como uma macro lisp é (essencialmente) um programa lisp que usa trechos de código como entrada e retorna código para substituir a "invocação" da macro, você pode estender seu repertório "primitivo" até onde quiser, geralmente terminando com um programa mais legível.
Para fazer o mesmo em C, você teria que escrever um pré-processador personalizado que consome sua fonte inicial (não muito C) e cuspa algo que um compilador C possa entender. Não é um caminho errado, mas não é necessariamente o mais fácil.
fonte
As macros Lisp permitem que você decida quando (se houver) alguma parte ou expressão será avaliada. Para colocar um exemplo simples, pense nos C's:
O que isso diz é: Avalie
expr1
e, se for verdade, avalieexpr2
etc.Agora tente transformar isso
&&
em uma função ... está certo, você não pode. Chamando algo como:Irá avaliar todos os três
exprs
antes de fornecer uma resposta, independentemente deexpr1
ser falsa!Com as macros lisp, você pode codificar algo como:
agora você tem um
&&
, que você pode chamar como uma função e ela não avaliará nenhuma forma que você passar para ela, a menos que todas sejam verdadeiras.Para ver como isso é útil, contraste:
e:
Outras coisas que você pode fazer com macros são criar novas palavras-chave e / ou mini-idiomas (confira a
(loop ...)
macro por exemplo), integrando outros idiomas ao lisp, por exemplo, você pode escrever uma macro que permita dizer algo como:E isso nem chega às macros do Reader .
Espero que isto ajude.
fonte
(and ...
avaliará expressões até que se avalie como falso; observe que os efeitos colaterais gerados pela avaliação falsa ocorrerão; somente as expressões subsequentes serão ignoradas.Acho que nunca vi macros do Lisp explicadas melhor do que esse sujeito: http://www.defmacro.org/ramblings/lisp.html
fonte
Pense no que você pode fazer em C ou C ++ com macros e modelos. São ferramentas muito úteis para gerenciar código repetitivo, mas são limitadas de maneiras bastante severas.
As macros Lisp e Lisp resolvem esses problemas.
Converse com qualquer pessoa que domine C ++ e pergunte quanto tempo eles gastaram aprendendo todos os truques de modelos necessários para realizar a metaprogramação de modelos. Ou todos os truques malucos em (excelentes) livros como o Modern C ++ Design , que ainda são difíceis de depurar e (na prática) não são portáveis entre compiladores do mundo real, mesmo que a linguagem tenha sido padronizada por uma década. Tudo isso desaparece se o idioma usado para a metaprogramação for o mesmo idioma usado para a programação!
fonte
Uma macro lisp usa um fragmento de programa como entrada. Este fragmento de programa é representado por uma estrutura de dados que pode ser manipulada e transformada da maneira que você desejar. No final, a macro gera outro fragmento de programa, e esse fragmento é o que é executado em tempo de execução.
O C # não possui um recurso de macro, no entanto, seria equivalente se o compilador analisasse o código em uma árvore do CodeDOM e passasse isso para um método, que o transformou em outro CodeDOM, que é compilado no IL.
Isso pode ser usado para implementar a sintaxe "sugar", como a
for each
cláusula -statementusing
-exexions, linqselect
-expressions e assim por diante, como macros que se transformam no código subjacente.Se o Java tivesse macros, você poderia implementar a sintaxe do Linq em Java, sem precisar que a Sun alterasse o idioma base.
Aqui está o pseudo-código de como uma macro de estilo lisp em C # para implementação
using
pode parecer:fonte
using
se parecer com isso ;)Como as respostas existentes fornecem bons exemplos concretos que explicam o que as macros alcançam e como, talvez ajude a reunir alguns dos pensamentos sobre por que o recurso de macro é um ganho significativo em relação a outros idiomas ; primeiro dessas respostas, depois uma ótima de outro lugar:
- Vatine
- Matt Curtis
- Miguel Ping
- Peter Seibel, em "Prático Common Lisp"
fonte
Não tenho certeza de que posso adicionar algumas dicas às postagens (excelentes) de todos, mas ...
As macros Lisp funcionam muito bem devido à natureza da sintaxe Lisp.
Lisp é uma linguagem extremamente regular (pense em tudo é uma lista ); macros permite que você trate dados e códigos da mesma forma (nenhuma análise de string ou outros hacks são necessários para modificar as expressões lisp). Você combina esses dois recursos e tem uma maneira muito limpa de modificar o código.
Edit: O que eu estava tentando dizer é que o Lisp é homoicônico , o que significa que a estrutura de dados de um programa lisp é escrita no próprio lisp.
Então, você acaba criando uma maneira de criar seu próprio gerador de código no topo da linguagem, usando toda a linguagem em si (por exemplo, em Java, você precisa abrir caminho com a tecelagem de códigos de bytes, embora algumas estruturas como a AspectJ permitam faça isso usando uma abordagem diferente, é fundamentalmente um hack).
Na prática, com as macros, você acaba criando seu próprio mini-idioma sobre o lisp, sem a necessidade de aprender idiomas ou ferramentas adicionais e usando todo o poder do próprio idioma.
fonte
S-expressions
, não listas.As macros Lisp representam um padrão que ocorre em quase qualquer projeto de programação considerável. Eventualmente, em um programa grande, você tem uma certa seção de código, na qual percebe que seria mais simples e menos propenso a erros escrever um programa que gera o código-fonte como texto no qual você pode simplesmente colar.
No Python, os objetos têm dois métodos
__repr__
e__str__
.__str__
é simplesmente a representação legível por humanos.__repr__
retorna uma representação que é um código Python válido, ou seja, algo que pode ser inserido no intérprete como Python válido. Dessa forma, você pode criar pequenos trechos de Python que geram código válido que pode ser colado na sua fonte atual.No Lisp, todo esse processo foi formalizado pelo sistema macro. Certamente, isso permite que você crie extensões para a sintaxe e faça todo tipo de coisas sofisticadas, mas sua utilidade real é resumida acima. Obviamente, ajuda que o sistema de macro Lisp permita que você manipule esses "trechos" com todo o poder de todo o idioma.
fonte
Em resumo, macros são transformações de código. Eles permitem introduzir muitas novas construções de sintaxe. Por exemplo, considere o LINQ em C #. No lisp, existem extensões de linguagem semelhantes que são implementadas por macros (por exemplo, construção de loop embutida, iterar). Macros diminuem significativamente a duplicação de código. As macros permitem incorporar «pequenos idiomas» (por exemplo, onde em c # / java se usaria xml para configurar, em lisp o mesmo pode ser alcançado com macros). Macros podem ocultar dificuldades no uso de bibliotecas.
Por exemplo, no lisp você pode escrever
e isso oculta todo o material do banco de dados (transações, fechamento de conexão adequado, busca de dados etc.), enquanto em C # isso requer a criação de SqlConnections, SqlCommands, adicionando SqlParameters a SqlCommands, fazendo loop em SqlDataReaders, fechando-os adequadamente.
fonte
Embora tudo isso explique o que são macros e até tenha exemplos interessantes, acho que a principal diferença entre uma macro e uma função normal é que o LISP avalia todos os parâmetros primeiro antes de chamar a função. Com uma macro ao contrário, o LISP passa os parâmetros não avaliados para a macro. Por exemplo, se você passar (+ 1 2) para uma função, a função receberá o valor 3. Se você passar isso para uma macro, ela receberá uma Lista (+ 1 2). Isso pode ser usado para fazer todos os tipos de coisas incrivelmente úteis.
Meça o tempo necessário para executar uma função transmitida. Com uma função, o parâmetro seria avaliado antes do controle ser passado para a função. Com a macro, você pode dividir seu código entre o início e o fim do cronômetro. O abaixo tem exatamente o mesmo código em uma macro e uma função e a saída é muito diferente. Nota: Este é um exemplo artificial e a implementação foi escolhida para que seja idêntica para destacar melhor a diferença.
fonte
Compreendi isso no livro de receitas comum do lisp, mas acho que explicou por que as macros do lisp são boas de uma maneira agradável.
"Uma macro é um pedaço comum de código Lisp que opera em outro suposto código Lisp, convertendo-o em (uma versão mais próxima) do executável Lisp. Isso pode parecer um pouco complicado, então vamos dar um exemplo simples. Suponha que você queira um versão do setq que define duas variáveis para o mesmo valor.Portanto, se você escrever
quando
z=8
x e y são definidos como 11. (Não consigo pensar em nenhum uso disso, mas é apenas um exemplo).Deveria ser óbvio que não podemos definir o setq2 como uma função. Se
x=50
ey=-5
, essa função receberia os valores 50, -5 e 11; não teria conhecimento de quais variáveis deveriam ser definidas. O que realmente queremos dizer é: quando você (o sistema Lisp) vê(setq2 v1 v2 e)
, trata-o como equivalente a(progn (setq v1 e) (setq v2 e))
. Na verdade, isso não está certo, mas servirá por enquanto. Uma macro nos permite fazer exatamente isso, especificando um programa para transformar o padrão de entrada(setq2 v1 v2 e)
"no padrão de saída(progn ...)
".Se você achou que isso era legal, continue lendo aqui: http://cl-cookbook.sourceforge.net/macros.html
fonte
setq2
como uma função sex
ey
são passados por referência. Não sei se é possível no CL, no entanto. Assim, para alguém que não sei Lisps ou CL, em particular, isso não é exemplo muito ilustrativo IMONo python, você tem decoradores, basicamente tem uma função que assume outra função como entrada. Você pode fazer o que quiser: chamar a função, fazer outra coisa, agrupar a chamada de função em uma versão de aquisição de recursos, etc., mas não poderá espiar dentro dessa função. Digamos que queremos torná-lo mais poderoso, digamos que seu decorador recebeu o código da função como uma lista, então você não pode apenas executar a função como está, mas agora pode executar partes dela, reordenar linhas da função etc.
fonte