O que torna as macros do Lisp tão especiais?


297

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.


2
By the way, há um processador de macro-style LISP para C # chamado Lemp: ecsharp.net/lemp ... JavaScript também tem um chamado Sweet.js: sweetjs.org
Qwertie

@Qwertie Sweetjs ainda funciona hoje em dia?
Fredrik.hjarner

Eu não o usei, mas o commit mais recente foi há seis meses ... bom o suficiente para mim!
Qwertie

Respostas:


308

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

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

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:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

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.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

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]onde xé 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))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Agora podemos executar na linha de comando:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

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 withsintaxe do C # . Ou sintaxe LINQ do .NET. No final, é isso que atrai as pessoas para o Lisp - a máxima flexibilidade.


51
+1 para implementar a compreensão da lista no Lisp, por que não?
Ckb 02/04/19

9
@ckb Na verdade LISP já tem uma macro compreensão da lista na biblioteca padrão: (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 )
Suzanne Dupéron

8
Eu sei que é bastante independente, mas estou me perguntando sobre a sintaxe e como a análise realmente funciona ... Digamos que eu chame lcomp dessa maneira (alterando o item thirs de "para" para "azertyuiop"): (lcomp x azertyuiop x in ( intervalo 10) se (= (% x 2) 0)) a macro ainda funcionará conforme o esperado? Ou o parâmetro "for" é usado no loop para que seja a string "for" quando chamada?
Dader

2
Então, loop é na verdade outra macro e é um símbolo especial usado nessa macro. Eu poderia ter definido facilmente a macro sem a macro de loop. No entanto, se eu tivesse, a postagem teria sido muito mais longa. A macro de loop e toda a sua sintaxe são definidas no CLtL .
Gte525u

3
Uma coisa que me deixa confuso com as macros de outras línguas é que elas são limitadas pela sintaxe da linguagem host. As macros Lispy podem interpretar sintaxe não Lispy. Quero dizer, imagine criar um haskell como sintaxe (sem parênteses) e interpretá-lo usando macros Lisp. Isso é possível e quais são os prós / contras do uso de macros em comparação ao uso de um lexer e analisador diretamente?
CMCDragonkai

105

Você encontrará um debate abrangente sobre a macro lisp aqui .

Um subconjunto interessante desse artigo:

Na maioria das linguagens de programação, a sintaxe é complexa. As macros precisam desmontar a sintaxe do programa, analisá-la e remontá-la. Eles não têm acesso ao analisador do programa, portanto, precisam depender de heurísticas e melhores palpites. Às vezes, a análise da taxa de corte está errada, e então eles quebram.

Mas Lisp é diferente. Lisp macros fazer ter acesso ao analisador, e é um analisador muito simples. Uma macro Lisp não recebe uma string, mas uma parte do código-fonte preparada na forma de uma lista, porque a fonte de um programa Lisp não é uma string; é uma lista. E os programas Lisp são realmente bons em desmontar listas e reuni-las novamente. Eles fazem isso de forma confiável, todos os dias.

Aqui está um exemplo estendido. O Lisp possui uma macro, chamada "setf", que executa a atribuição. A forma mais simples de setf é

  (setf x whatever)

que define o valor do símbolo "x" para o valor da expressão "seja qual for".

Lisp também tem listas; você pode usar as funções "car" e "cdr" para obter o primeiro elemento de uma lista ou o restante da lista, respectivamente.

Agora, e se você quiser substituir o primeiro elemento de uma lista por um novo valor? Existe uma função padrão para fazer isso e, incrivelmente, seu nome é ainda pior que "carro". É "rplaca". Mas você não precisa se lembrar de "rplaca", porque você pode escrever

  (setf (car somelist) whatever)

para definir o carro da lista de som.

O que realmente está acontecendo aqui é que "setf" é uma macro. No momento da compilação, ele examina seus argumentos e vê que o primeiro tem a forma (car ALGO). Diz para si mesmo "Oh, o programador está tentando armar o carro de algo. A função a ser usada para isso é 'rplaca'". E discretamente reescreve o código no local para:

  (rplaca somelist whatever)

5
setf é uma boa ilustração do poder das macros, obrigado por incluí-lo.
Joel

Eu gosto do destaque .. porque a fonte de um programa Lisp não é uma string; é uma lista. ! É a principal razão pela qual a macro LISP é superior à maioria das outras devido aos seus parênteses?
Estudante

@ Estudante Suponho que sim: books.google.fr/… sugere que você está certo.
VonC

55

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.


41

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:

expr1 && expr2 && expr3 ...

O que isso diz é: Avalie expr1e, se for verdade, avalie expr2etc.

Agora tente transformar isso &&em uma função ... está certo, você não pode. Chamando algo como:

and(expr1, expr2, expr3)

Irá avaliar todos os três exprsantes de fornecer uma resposta, independentemente de expr1ser falsa!

Com as macros lisp, você pode codificar algo como:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

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:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

e:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

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:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

E isso nem chega às macros do Reader .

Espero que isto ajude.


Eu acho que deveria ser: "(aplique &&, @ exprs); isso pode estar errado!"
Svante

1
@svante - em dois aspectos: primeiro, && é uma macro, não uma função; aplicar apenas funciona em funções. segundo, aplique pegue uma lista de argumentos para passar, então você deseja um de "(funcall fn, @ exprs)", "(aplique fn (lista, @ exprs)" ou "(aplique fn, @ exprs nil)", não "(aplicar fn, @ exprs)".
Aaron

(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.
Ocho


10

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.

  • A sintaxe limitada de macro / modelo restringe seu uso. Por exemplo, você não pode escrever um modelo que se expanda para algo diferente de uma classe ou função. Macros e modelos não podem manter facilmente dados internos.
  • A sintaxe complexa e muito irregular de C e C ++ dificulta a gravação de macros muito gerais.

As macros Lisp e Lisp resolvem esses problemas.

  • Macros Lisp são escritas em Lisp. Você tem todo o poder do Lisp para escrever a macro.
  • Lisp tem uma sintaxe muito regular.

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!


11
Bem, para ser justo, o problema com a metaprogramação de modelos C ++ não é que a linguagem de metaprogramação seja diferente , mas que é horrível - ela não foi tão projetada quanto descoberta no que se destinava a ser uma funcionalidade de modelo muito mais simples.
Brooks Moses

@Brooks Sure. Recursos emergentes nem sempre são ruins. Infelizmente, em um idioma lento de comitê, é difícil corrigi-los quando o são. É uma pena que muitos dos novos recursos úteis modernos do C ++ sejam escritos em uma linguagem que poucos conseguem ler, e há uma enorme lacuna entre o programador médio e o "sumo sacerdote".
Matt Curtis

2
@ downvoter: se houver algo errado com a minha resposta, deixe um comentário para que todos possamos compartilhar o conhecimento.
Matt Curtis

10

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 eachcláusula -statement using-exexions, linq select-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 usingpode parecer:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

Agora que há, na verdade, é um processador de macro Lisp-estilo para C #, gostaria de salientar que uma macro para usingse parecer com isso ;)
Qwertie

9

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:

... em C, você teria que escrever um pré-processador personalizado [que provavelmente se qualificaria como um programa C suficientemente complicado ] ...

- Vatine

Converse com qualquer pessoa que domine C ++ e pergunte quanto tempo eles gastaram aprendendo todos os truques de modelos necessários para executar a metaprogramação de modelos [que ainda não é tão poderosa].

- Matt Curtis

... em Java, você deve abrir seu caminho com a tecelagem de códigos de bytes, embora alguns frameworks como o AspectJ permitam fazer isso usando uma abordagem diferente, é fundamentalmente um hack.

- Miguel Ping

DOLIST é semelhante ao foreach de Perl ou ao Python de. O Java adicionou um tipo semelhante de construção de loop com o loop for "aprimorado" no Java 1.5, como parte do JSR-201. Observe a diferença que as macros fazem. Um programador Lisp que percebe um padrão comum em seu código pode escrever uma macro para obter uma abstração desse padrão no nível de origem. Um programador Java que percebe o mesmo padrão precisa convencer a Sun de que vale a pena adicionar essa abstração específica à linguagem. Em seguida, a Sun precisa publicar um JSR e reunir um "grupo de especialistas" em todo o setor para organizar tudo. Esse processo - segundo a Sun - leva em média 18 meses. Depois disso, todos os escritores do compilador precisam atualizar seus compiladores para dar suporte ao novo recurso. E mesmo quando o compilador favorito do programador Java suporta a nova versão do Java, eles provavelmente "ainda" não podem usar o novo recurso até que possam quebrar a compatibilidade de origem com versões mais antigas do Java. Portanto, um aborrecimento que os programadores do Common Lisp podem resolver por si mesmos em cinco minutos assola os programadores de Java por anos.

- Peter Seibel, em "Prático Common Lisp"


8

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.


1
É um comentário perspicaz, no entanto, essa idéia de que "tudo é uma lista" pode assustar os recém-chegados. Para entender uma lista, você precisa entender contras, carros, cdrs e células. Mais precisamente, o Lisp é feito S-expressions, não listas.
Ribamar # 31/18

6

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.


1
Seu primeiro parágrafo é muito claro para quem está de fora do Lisp, o que é importante.
Curinga

5

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

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

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.


3

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.

  • Adicionando uma nova estrutura de controle, por exemplo, loop ou a desconstrução de uma lista
  • 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.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

Aliás, Scala adicionou macros ao idioma. Embora eles não tenham a beleza das macros do Lisp porque a linguagem não é homoicônica, vale a pena analisá-la e as árvores de sintaxe abstrata que eles fornecem podem ser mais fáceis de usar no final. É muito cedo para dizer qual macro sistema eu prefiro.
Joerg Schmuecker 15/08/19

2
"O LISP passa os parâmetros não avaliados para a macro", finalmente uma resposta que diz claramente. mas você esqueceu a segunda metade disso: e o resultado de uma macro é um código transformado que será avaliado inteiro pelo sistema no lugar do original, como se estivesse lá em primeiro lugar (a menos que ele próprio seja novamente uma chamada para uma macro, que também será transformada, por essa macro dessa vez).
Will Ness

0

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

(setq2 x y (+ z 3))

quando z=8x 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=50e y=-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


1
É possível definir setq2como uma função se xe ysã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 IMO
neoascetic

A passagem do argumento @neoascetic CL é apenas por valor (é por isso que ele precisa de macros em primeiro lugar). alguns valores são indicadores (como listas).
Will Ness

-5

No 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.

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.