¿Qué tan útiles son las macros de Lisp?


22

Common Lisp le permite escribir macros que realizan cualquier transformación de origen que desee.

Scheme le ofrece un sistema higiénico de coincidencia de patrones que también le permite realizar transformaciones. ¿Qué tan útiles son las macros en la práctica? Paul Graham dijo en Beating the Averages que:

El código fuente del editor de Viaweb era probablemente de un 20-25% de macros.

¿Qué tipo de cosas terminan haciendo las personas con macros?


Creo que esto definitivamente cae en una buena subjetiva , he editado su pregunta para formatear. Esto podría ser un duplicado, pero no pude encontrar uno.
Tim Post

1
Supongo que todo lo repetitivo que no parece encajar en una función.

2
Puede usar macros para convertir Lisp en cualquier otro idioma, con cualquier sintaxis y semántica: bit.ly/vqqvHU
SK-logic

Aquí vale la pena mirar programmers.stackexchange.com/questions/81202/… , pero no es un duplicado.
David Thornley

Respuestas:


15

Eche un vistazo a esta publicación de Matthias Felleisen en la lista de discusión de LL1 en 2002. Sugiere tres usos principales para las macros:

  1. Sublenguajes de datos : puedo escribir expresiones de aspecto simple y crear listas / matrices / tablas anidadas complejas con comillas, comillas, etc. perfectamente vestidas con macros.
  2. Construcciones de unión : puedo introducir nuevas construcciones de unión con macros. Eso me ayuda a deshacerme de las lambdas y colocar las cosas más juntas que pertenecen juntas.
  3. Reordenamiento de la evaluación : puedo introducir construcciones que retrasan / posponen la evaluación de expresiones según sea necesario. Piense en bucles, nuevos condicionales, retraso / fuerza, etc. [Nota: en Haskell o en cualquier lenguaje perezoso, este es innecesario].

18

Principalmente uso macros para agregar nuevas construcciones de lenguaje que ahorran tiempo, que de lo contrario requerirían un montón de código repetitivo.

Por ejemplo, recientemente me encontré queriendo un imperativo for-loopsimilar a C ++ / Java. Sin embargo, al ser un lenguaje funcional, Clojure no vino con uno fuera de la caja. Así que lo implementé como una macro:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

Y ahora puedo hacer:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

Y ahí lo tiene: una nueva construcción de lenguaje de tiempo de compilación de propósito general en seis líneas de código.


13

¿Qué tipo de cosas terminan haciendo las personas con macros?

Escritura de extensiones de lenguaje o DSL's.

Para tener una idea de esto en lenguajes similares a Lisp, estudie Racket , que tiene varias variantes de idioma: Typed Racket, R6RS y Datalog.

Consulte también el lenguaje Boo, que le brinda acceso a la canalización del compilador para el propósito específico de crear lenguajes específicos de dominio a través de macros.


4

Aquí hay unos ejemplos:

Esquema:

  • definepara definiciones de funciones. Básicamente, hace una forma más corta de definir una función.
  • let para crear variables con ámbito léxico.

Clojure:

  • defn, de acuerdo con sus documentos:

    Igual que (nombre de def (fn [params *] exprs *)) o (nombre de def (fn ([params *] exprs *) +)) con cualquier cadena de documentos o atributos agregados a los metadatos var

  • for: lista de comprensiones
  • defmacro: irónico?
  • defmethod, defmulti: trabajando con métodos múltiples
  • ns

Muchas de estas macros hacen que sea mucho más fácil escribir código en un nivel más abstracto. Pienso que las macros son similares, en muchos sentidos, a la sintaxis en no Lisps.

La biblioteca de trazado Incanter proporciona macros para algunas manipulaciones de datos complejas.


4

Las macros son útiles para incrustar algunos patrones.

Por ejemplo, Common Lisp no define el whileciclo pero tienedo que puede usarse para definirlo.

Aquí hay un ejemplo de On Lisp .

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Esto imprimirá "12345678910", y si intenta ver qué sucede con macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Esto devolverá:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

Esta es una macro simple, pero como se dijo antes, generalmente se usan para definir nuevos lenguajes o DSL, pero a partir de este simple ejemplo, ya puede intentar imaginar qué puede hacer con ellos.

La loopmacro es un buen ejemplo de lo que pueden hacer las macros.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Common Lisp tiene otro tipo de macros llamado macro lector que se puede usar para modificar la forma en que el lector interpreta el código, es decir, puede usarlas para usar # {y #} tiene delimitadores como # (y #).


3

Aquí hay uno que uso para depurar (en Clojure):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

Tuve que lidiar con una tabla hash enrollada a mano en C ++, donde el getmétodo tomó una referencia de cadena no constante como argumento, lo que significa que no puedo llamarlo con un literal. Para facilitar el tratamiento, escribí algo como lo siguiente:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

Si bien es poco probable que surja algo como este problema, me parece particularmente agradable que pueda tener macros que no evalúen sus argumentos dos veces, por ejemplo, introduciendo un verdadero enlace . (Admitido, aquí podría haberlo evitado).

También recurro al truco terriblemente feo de envolver cosas en un do ... while (false) tal manera que puedas usarlo en la parte de un if y aún tener la parte de más que funcione como se esperaba. No necesita esto en lisp, que es una función de macros que operan en árboles de sintaxis en lugar de cadenas (o secuencias de tokens, creo, en el caso de C y C ++) que luego se analizan.

Hay algunas macros de subprocesamiento incorporadas que se pueden usar para reorganizar su código de modo que se lea de manera más limpia ('subprocesamiento' como en 'sembrar su código', no paralelismo). Por ejemplo:

(->> (range 6) (filter even?) (map inc) (reduce *))

Toma la primera forma, (range 6)y lo convierte en el último argumento de la siguiente forma, (filter even?)que a su vez se convierte en el último argumento de la siguiente forma y así sucesivamente, de modo que lo anterior se reescribe en

(reduce * (map inc (filter even? (range 6))))

Creo que el primero se lee mucho más claramente: "toma estos datos, haz esto, luego haz eso, luego haz lo otro y listo", pero eso es subjetivo; algo que es objetivamente cierto es que lees las operaciones en la secuencia en que se realizan (ignorando la pereza).

También hay una variante que inserta la forma anterior como el primer argumento (en lugar del último). Un caso de uso es la aritmética:

(-> 17 (- 2) (/ 3))

Se lee como "toma 17, resta 2 y divide entre 3".

Hablando de aritmética, puede escribir una macro que analice la notación infijada, de modo que pueda decir, por ejemplo, (infix (17 - 2) / 3)y escupe (/ (- 17 2) 3)cuál tiene la desventaja de ser menos legible y la ventaja de ser una expresión válida de lisp. Esa es la parte del sublenguaje DSL / datos.


1
La aplicación de funciones tiene mucho más sentido para mí que los subprocesos, pero seguramente es una cuestión de hábito. Buena respuesta.
coredump
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.