¿Es justo decir que “las macros no se componen”?

7

En esta publicación del blog aphyr (que es un programador brillante) declara :

  

Las macros de Clojure vienen con algunas restricciones importantes. Debido a que se expanden antes de la evaluación, las macros son invisibles para las funciones. No se pueden componer funcionalmente, no se puede (map or ...) , por ejemplo.

El ejemplo clásico es:

(reduce and [true true false true])
;RuntimeException

Sin embargo, podemos escribir:

(reduce #(and %1 %2) [true true false true])
;false

¿Las macros no componen una reclamación válida? Seguramente el único punto válido es que las macros no son funciones de primera clase y no deben tratarse como tales.

    
pregunta hawkeye 28.12.2013 - 23:34

1 respuesta

7

Creo que esto depende de lo que quieres decir con "componer". Yo diría que en su mayoría es cierto, pero se puede tomar demasiado lejos.

Primero, note que en la cita que citó aphyr no dijo "las macros no componen"; la frase era "no se puede componer funcionalmente". Esa afirmación más limitada es indiscutiblemente cierta. Las macros en Clojure no son valores [1]. No pueden pasarse como argumentos a otras funciones o macros, no pueden devolverse como resultado de cálculos y no pueden almacenarse en estructuras de datos.

Por supuesto, hay formas de evitar estas limitaciones. Además del ejemplo de #(and %1 %2) , hay un sentido en el que el código siguiente representa la composición.

(ns some.macros
  (:refer-clojure :exclude [when when-not]))

(defmacro when [test & body]
  '(cond ~test (do [email protected])))

(defmacro when-not [test & body]
  '(when (not ~test) [email protected]))

Usé intencionalmente cond en lugar de if en la definición de when porque cond es en sí mismo una macro.

Pero, volviendo al punto más amplio, #(and %1 %2) no es realmente un argumento que componen las macros, es un argumento que puede usar macros dentro de las funciones. Y, dado que tenemos muchas formas poderosas de trabajar con funciones, esta es generalmente la mejor manera de hacerlo. Use macros (cuando las funciones no son apropiadas) para abstraer el código de repetición, código repetitivo, pero cuando sea posible, en última instancia, exponga la mayor cantidad posible de su API como funciones para que puedan almacenarse en mapas hash, mapeados sobre secuencias de widgets , negado con complement , yuxtapuesto con juxt , y así sucesivamente.

[1] Sería difícil que fuera de otra manera. Imagínese si las macros fueran valores y pudieran pasarse como argumentos a funciones. ¿Cómo podría compilar el código que usó tal macro, dado que su expansión no se conocerá hasta el tiempo de ejecución (y podría variar entre las invocaciones de la función)?

Addendum

Como punto de interés, tenga en cuenta que las macros son funciones , que el compilador las trata de forma especial.

Mira esto:

some.macros> (defn when [&form &env test & body]
               '(cond ~test (do [email protected])))
#<[email protected]:
  #<macros$eval7806$when__7807 [email protected]>>
some.macros> (when nil nil true 1 2)
(clojure.core/cond true (do 1 2))
some.macros> (. #'when (setMacro))
nil
some.macros> (when true 1 2)
2

Esto es, de hecho, exactamente cómo funciona defmacro . (Consulte aquí para obtener una explicación de los pseudo-argumentos &form y &env ).

    
respondido por el jbm 29.12.2013 - 04:33

Lea otras preguntas en las etiquetas