Decoradores Python e macros Lisp

15

Ao procurar decoradores Python, alguém fez a afirmação de que eles são tão poderosos quanto as macros Lisp (particularmente o Clojure).

Olhando para os exemplos dados em PEP 318 , parece-me que são apenas maneira sofisticada de usar funções antigas de alta ordem no Lisp:

def attrs(**kwds):
    def decorate(f):
        for k in kwds:
            setattr(f, k, kwds[k])
        return f
    return decorate

@attrs(versionadded="2.2",
       author="Guido van Rossum")
def mymethod(f):
    ...

Eu não vi nenhum código sendo transformado em nenhum dos exemplos, como descrito em Anatomia de uma Macro Clojure . Além disso, a homoiconicidade do Python poderia tornar as transformações de código impossíveis.

Então, como esses dois se comparam e você pode dizer que eles são iguais no que você pode fazer? As evidências parecem apontar contra isso.

Editar: Com base em um comentário, estou procurando duas coisas: comparação em "tão poderosa quanto" e "tão fácil de fazer coisas incríveis com".

    
por Profpatsch 09.10.2013 / 10:13
fonte

4 respostas

16

Um decorador é basicamente apenas uma função .

Exemplo no Common Lisp:

(defun attributes (keywords function)
  (loop for (key value) in keywords
        do (setf (get function key) value))
  function)

Acima, a função é um símbolo (que seria retornado por DEFUN ) e colocamos os atributos na lista de propriedades do símbolo .

Agora podemos escrever em torno de uma definição de função:

(attributes
  '((version-added "2.2")
    (author "Rainer Joswig"))

  (defun foo (a b)
    (+ a b))

)  

Se quisermos adicionar uma sintaxe sofisticada como no Python, escrevemos uma macro de leitura . Uma macro de leitura nos permite programar no nível da sintaxe da expressão-s:

(set-macro-character
 #\@
 (lambda (stream char)
   (let ((decorator (read stream))
         (arg       (read stream))
         (form      (read stream)))
     '(,decorator ,arg ,form))))

Podemos então escrever:

@attributes'((version-added "2.2")
             (author "Rainer Joswig"))
(defun foo (a b)
  (+ a b))

O leitor Lisp lê acima para:

(ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                    (AUTHOR "Rainer Joswig")))
            (DEFUN FOO (A B) (+ A B)))

Agora temos uma forma de decoradores no Common Lisp.

Combinando macros e macros de leitura.

Na verdade, eu faria acima da tradução em código real usando uma macro, não uma função.

(defmacro defdecorator (decorator arg form)
  '(progn
     ,form
     (,decorator ,arg ',(second form))))

(set-macro-character
 #\@
 (lambda (stream char)
   (declare (ignore char))
   (let* ((decorator (read stream))
          (arg       (read stream))
          (form      (read stream)))
     '(defdecorator ,decorator ,arg ,form))))

O uso é como acima com a mesma macro de leitura. A vantagem é que o compilador Lisp ainda o vê como um chamado formulário de nível superior - o compilador de arquivos * trata os formulários de nível superior especialmente, por exemplo, adiciona informações sobre eles no tempo de compilação ambiente . No exemplo acima, podemos ver que a macro analisa o código-fonte e extrai o nome.

O leitor Lisp lê o exemplo acima em:

(DEFDECORATOR ATTRIBUTES
  (QUOTE ((VERSION-ADDED "2.2")
           (AUTHOR "Rainer Joswig")))
  (DEFUN FOO (A B) (+ A B)))

O qual, em seguida, obtém a macro expandida em:

(PROGN (DEFUN FOO (A B) (+ A B))
       (ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                           (AUTHOR "Rainer Joswig")))
                   (QUOTE FOO)))

As macros são muito diferentes das macros de leitura .

As macros obtêm o código-fonte passado, podem fazer o que quiserem e depois retornar o código-fonte. A origem de entrada não precisa ser um código Lisp válido. Pode ser qualquer coisa e pode ser escrita de forma totalmente diferente. O resultado tem que ser um código Lisp válido então. Mas, se o código gerado também estiver usando uma macro, a sintaxe do código incorporado na chamada de macro poderá ser novamente uma sintaxe diferente. Um exemplo simples: é possível escrever uma macro matemática que aceite algum tipo de sintaxe matemática:

(math y = 3 x ^ 2 - 4 x + 3)

A expressão y = 3 x ^ 2 - 4 x + 3 não é um código Lisp válido, mas a macro poderia, por exemplo, analisá-lo e retornar um código Lisp válido como este:

(setq y (+ (* 3 (expt x 2))
           (- (* 4 x))
           3))

Existem muitos outros casos de uso de macros no Lisp.

    
por 11.10.2013 / 21:11
fonte
7

Em Python (a linguagem), os decoradores não podem modificar a função, apenas envolvê-la, então eles são definitivamente menos poderosos que as macros lisp.

No CPython (o intérprete) os decoradores podem modificar a função porque eles têm acesso ao bytecode, mas a função é compilada primeiro e que pode ser manipulada pelo decorador, então não é possível alterar a sintaxe, uma coisa lisp-macro-equivalente precisaria fazer.

Note que os modernos lisps não usam expressões S como bytecode, então as macros que trabalham em listas de expressão-S trabalham antes da compilação de bytecode como mencionado acima, em python o decorador é executado após ele.

    
por 09.10.2013 / 10:42
fonte
4

É muito difícil usar decoradores Python para introduzir novos mecanismos de fluxo de controle.

É quase trivial usar as macros Common Lisp para introduzir novos mecanismos de fluxo de controle.

A partir disso, provavelmente não são igualmente expressivos (eu escolho interpretar "poderoso" como "expressivo", já que eu penso o que eles realmente querem dizer).

    
por 09.10.2013 / 13:32
fonte
0

Certamente é funcionalidade relacionada, mas a partir de um decorador Python não é trivial modificar o método que está sendo chamado (esse seria o parâmetro f em seu exemplo). Para modificá-lo você poderia enlouquecer com o módulo ast ), mas você estaria em uma programação bastante complicada.

As coisas ao longo desta linha foram feitas: confira o pacote macropy para alguns exemplos realmente alucinantes.

    
por 09.10.2013 / 10:37
fonte