Decoradores Python y macros Lisp

18

Al mirar los decoradores de Python, alguien hizo la declaración de que son tan poderosos como las macros de Lisp (particularmente Clojure).

Al observar los ejemplos dados en PEP 318, me parece que son solo una forma elegante de usar funciones simples de orden superior en 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):
    ...

No he visto ningún código que se transforme en ninguno de los ejemplos, como se describe en Anatomy of a Clojure Macro . Además, la falta de homoiconicidad de Python podría hacer que las transformaciones de código sean imposibles.

Entonces, ¿cómo se comparan estos dos y puede decir que son casi iguales en lo que puede hacer? La evidencia parece apuntar en su contra.

Editar: Basado en un comentario, estoy buscando dos cosas: comparación de "tan poderoso como" y "tan fácil de hacer cosas increíbles con".

Profpatsch
fuente
12
Por supuesto, los decoradores no son macros reales. No pueden traducir un lenguaje arbitrario (con una sintaxis totalmente diferente) a python. Quienes afirman lo contrario simplemente no entienden las macros.
SK-logic
1
Python no es homoicónico, sin embargo, es muy, muy dinámico. La homoiconicidad solo se requiere para poderosas transformaciones de código si desea hacerlo en tiempo de compilación; si tiene soporte para acceso directo al AST compilado y las herramientas para alterarlo, puede hacerlo en tiempo de ejecución independientemente de la sintaxis del lenguaje. Dicho esto, "tan poderoso como" y "tan fácil de hacer cosas increíbles con" son conceptos muy diferentes.
Phoshi
Tal vez debería cambiar la pregunta a "tan fácil de hacer cosas increíbles" entonces. ;)
Profpatsch
Tal vez alguien pueda piratear alguna función de orden superior de Clojure comparable al ejemplo de Python anterior. Lo intenté pero crucé por mi mente en el proceso. Dado que el ejemplo de Python usa atributos de objeto, esto tiene que ser un poco diferente.
Profpatsch
@Phoshi La alteración del AST compilado en tiempo de ejecución se conoce como: código auto modificable .
Kaz

Respuestas:

16

Un decorador es básicamente solo una función .

Ejemplo en Common Lisp:

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

En la parte superior, la función es un símbolo (que sería devuelto por DEFUN) y colocamos los atributos en la lista de propiedades del símbolo .

Ahora podemos escribirlo alrededor de una definición de función:

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

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

)  

Si queremos agregar una sintaxis elegante como en Python, escribimos una macro de lector . Una macro lectora nos permite programar en el nivel de sintaxis de s-expresión:

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

Entonces podemos escribir:

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

El lector Lisp lee arriba para:

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

Ahora tenemos una forma de decoradores en Common Lisp.

Combinando macros y macros de lector.

En realidad, haría la traducción anterior en código real usando una macro, no una función.

(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))))

El uso es como el anterior con la misma macro de lector. La ventaja es que el compilador de Lisp todavía lo ve como un llamado formulario de nivel superior : el compilador de archivos * trata especialmente los formularios de nivel superior, por ejemplo, agrega información sobre ellos en el entorno de tiempo de compilación . En el ejemplo anterior, podemos ver que la macro busca en el código fuente y extrae el nombre.

El lector Lisp lee el ejemplo anterior en:

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

Que luego se amplía macro en:

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

Las macros son muy diferentes de las macros de lector .

Las macros obtienen el código fuente, pueden hacer lo que quieran y luego devolver el código fuente. La fuente de entrada no necesita ser un código Lisp válido. Puede ser cualquier cosa y podría escribirse totalmente diferente. El resultado tiene que ser un código Lisp válido entonces. Pero si el código generado también usa una macro, entonces la sintaxis del código incrustado en la llamada a la macro podría ser nuevamente una sintaxis diferente. Un ejemplo simple: se podría escribir una macro matemática que aceptaría algún tipo de sintaxis matemática:

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

La expresión y = 3 x ^ 2 - 4 x + 3no es un código Lisp válido, pero la macro podría, por ejemplo, analizarlo y devolver un código Lisp válido como este:

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

Hay muchos otros casos de uso de macros en Lisp.

Rainer Joswig
fuente
8

En Python (el lenguaje) los decoradores no pueden modificar la función, solo envolverla, por lo que definitivamente son mucho menos potentes que las macros lisp.

En CPython (el intérprete), los decoradores pueden modificar la función porque tienen acceso al código de bytes, pero la función se compila primero y el decorador puede manipularla, por lo que no es posible alterar la sintaxis, cosa lisp-macro -equivalente tendría que hacer.

Tenga en cuenta que los lisps modernos no usan expresiones S como bytecode, por lo que las macros que trabajan en listas de expresiones S definitivamente funcionan antes de la compilación de bytecode como se señaló anteriormente, en python el decorador se ejecuta después.

Jan Hudec
fuente
1
No necesita modificar la función. Solo necesita leer el código de la función de alguna forma (en la práctica, esto significa bytecode). No es que esto lo haga más práctico.
2
@delnan: Técnicamente, lisp tampoco lo modifica; lo está usando como fuente para generar uno nuevo y también lo haría python, sí. El problema radica en la ausencia de lista de tokens o AST y el hecho de que el compilador ya se quejó de algunas cosas que de otro modo podría permitir en la macro.
Jan Hudec
4

Es bastante difícil usar decoradores Python para introducir nuevos mecanismos de flujo de control.

Es casi trivial usar macros Common Lisp para introducir nuevos mecanismos de control de flujo.

De esto, probablemente se deduce que no son igualmente expresivos (elijo interpretar "poderoso" como "expresivo", ya que creo que lo que realmente quieren decir).

Vatine
fuente
Me atrevo a decirs/quite hard/impossible/
@delnan Bueno, yo no iría bastante tan lejos como para decir "imposible", pero que sin duda tendría que trabajar en ello.
Vatine
0

Sin duda, está relacionada con la funcionalidad, pero desde un decorador de Python no es trivial modificar el método que se está llamando (ese sería el fparámetro en su ejemplo). Para modificarlo, podría volverse loco con el módulo ast ), pero estaría en una programación bastante complicada.

Sin embargo, se han hecho cosas a lo largo de esta línea: consulte el paquete macropy para ver algunos ejemplos realmente alucinantes.

Jacob Oscarson
fuente
3
Incluso las astcosas de transformación en python no son iguales a las macros de Lisp. Con Python, el lenguaje fuente debe ser Python, con las macros Lisp el lenguaje fuente transformado por una macro puede ser, literalmente, cualquier cosa. Por lo tanto, la metaprogramación de Python solo es adecuada para cosas simples (como AoP), mientras que la metaprogramación Lisp es útil para implementar potentes compiladores eDSL.
SK-logic
1
La cuestión es que la macropy no se implementa utilizando decoradores. Utiliza la sintaxis del decorador (porque debe usar una sintaxis de Python válida), pero se implementa secuestrando el proceso de compilación de bytes desde un enlace de importación.
Jan Hudec
@ SK-logic: en Lisp, el lenguaje fuente también debe ser lisp. La sintaxis de Just Lisp es muy simple pero flexible, mientras que la sintaxis de Python es mucho más compleja y no tan flexible.
Jan Hudec
1
@ JanHudec, en el lenguaje fuente de Lisp puede tener cualquier sintaxis (realmente, cualquiera ) - vea las macros del lector.
SK-logic