2010-12-30 10 views
6

Estuve conversando con un colega recientemente y traté de contarle sobre la belleza de (Común) Lisp. Traté de explicar las macros de alguna manera, ya que considero las macros como una de las características más importantes de Lisp, pero fallé bastante miserablemente. No pude encontrar un buen ejemplo que fuera breve, conciso y comprensible para un programador "simple mortal" (década de experiencia en Java, un tipo brillante en general, pero muy poca experiencia con idiomas de "orden superior").¿Hay un ejemplo simple para explicar las macros de Lisp a un programador "genérico"?

¿Cómo explicarías las macros de Lisp con el ejemplo si es necesario?

+0

Relacionados: http://stackoverflow.com/questions/267862/what-makes-lisp-macros-so-special – jball

+3

Creo que una de las mejores introducciones a las macros es en [Practical Common Lisp] (http: // gigamonkeys.com/book/practical-a-simple-database.html). Es uno de los primeros capítulos, por lo que no asume demasiado, y no es tan largo. Todo el libro es excelente, muy recomendable para cualquier persona, programador "genérico" o no (¡también hay capítulos sobre programación genérica!). – spacemanaki

Respuesta

6

Nueva WHILE

Su diseñador del lenguaje olvidó una sentencia while. Lo has enviado por correo varias veces. Sin éxito. Esperaste desde la versión de idioma 2.5, 2.6 a 3.0. No pasó nada ...

En Lisp:

(defmacro tiempo ... inserte su aplicación, mientras que aquí ...)

Hecho.

La implementación trivial utilizando LOOP toma un minuto.

La generación de código a partir de especificaciones

Entonces es posible que desee analizar registros detallados de llamadas (CDR). Usted tiene nombres de registro con descripciones de campo. Ahora puedo escribir clases y métodos para cada uno de ellos. También podría inventar algún formato de configuración, analizar un archivo de configuración y crear las clases. En Lisp escribiría una macro que genera el código a partir de una descripción compacta.

Vea Domain Specific Languages in Lisp, un screencast que muestra un ciclo de desarrollo típico de un boceto de trabajo a una simple generalización basada en macro.

Código reescribir

Imagine que tiene para acceder a las ranuras de los objetos que utilizan funciones getter. Ahora imagine que necesita acceder a algunos objetos varias veces en alguna región de código. Por alguna razón, el uso de variables temporales no es una solución.

... 
... (database-last-user database) ... 
... 

Ahora podría escribir una macro WITH-GETTER que introduzca un símbolo para la expresión getter.

(with-getters (database (last-user database-last-user)) 
    ... 
    ... last-user 
    ...) 

La macro reescribiría la fuente dentro del bloque adjunto y reemplazará todos los símbolos especificados con la expresión getter.

+0

+1 por _forgot_. :-) (El resto de la respuesta también es buena, pero me gustó especialmente). –

1

No conozco CL lo suficiente, pero ¿lo harán las macros de Scheme? Aquí está un bucle while en el Esquema:

(define-syntax while 
    (syntax-rules() 
    ((while pred body ...) 
    (let loop() 
     (if pred (begin body ... (loop))))))) 

En este caso, el ejemplo demuestra que se puede escribir fácilmente sus propias estructuras de control mediante macros. foof-loop es una colección de construcciones de bucle aún más útiles (probablemente nada nuevo dado los de CL, pero aún así es bueno para una demostración).


Otro caso de uso: selección de valores fuera de las listas asociativas. Digamos que los usuarios pasan una alista como opciones para su función. Puede seleccionar fácilmente los valores a cabo mediante el uso de esta macro:

(define-syntax let-assq 
    (syntax-rules() 
    ((let-assq alist (key) body ...) 
    (let ((key (assq-ref alist 'key))) 
     body ...)) 
    ((let-assq alist (key rest ...) body ...) 
    (let ((key (assq-ref alist 'key))) 
     (let-assq alist (rest ...) body ...))))) 

;; Guile built-in 
(define (assq-ref alist key) 
    (cond ((assq key alist) => cdr) 
     (else #f))) 

Ejemplo de uso:

(define (binary-search tree needle (lt? <)) 
    (let loop ((node tree)) 
    (and node 
     (let-assq node (value left right) 
      (cond ((lt? needle value) (loop left)) 
       ((lt? value needle) (loop right)) 
       (else value)))))) 

Aviso cómo el let-assq macro permite recoger las llaves value, left y right del "nodo" sin tener que escribir un formulario mucho más largo let.

7

Desde mi experiencia, las macros causan la mejor impresión en las personas cuando ven cómo ayuda a producir el código, que no se puede hacer mediante los procedimientos u otras construcciones. Muy a menudo estas cosas pueden describirse como:

<common code> 
<specific code> 
<other common code> 

donde <common code> es siempre la misma. Estos son algunos ejemplos de dicho esquema:

1. La macro time. código en un lenguaje sin macros se verá algo como esto:

int startTime = getCurrentTime(); 
<actual code> 
int endTime = getCurrentTime(); 
int runningTime = endTime - startTime; 

no se puede poner todo el código común de procedimiento, ya que se envuelve alrededor de código real. (OK, puede hacer un procedimiento y pasar el código real en la función lambda, si el idioma lo admite, pero no siempre es conveniente).
Y, a medida que más probablemente sabe, en Lisp que acaba de crear time macro y pasar a código real que:

(time 
    <actual code>) 

2. Transacciones. Solicite al programador de Java que escriba el método para SELECT simple con JDBC; tomará de 14 a 17 líneas e incluirá el código para abrir la conexión y transacción, cerrarlas, varias declaraciones try-catch-finally anidadas y solo 1 o 2 líneas de código único.
En Lisp simplemente escriba macro with-connection y reduzca el código a 2-3 líneas.

3. Sincronización. OK, Java, C# y la mayoría de los lenguajes modernos ya tienen afirmaciones para él, pero ¿qué hacer si su lenguaje no tiene una construcción así? ¿O si desea introducir un nuevo tipo de sincronización como STM? Nuevamente, debe escribir una clase separada para esta tarea y trabajar con ella manualmente, es decir, poner un código común alrededor de cada enunciado que quiera sincronizar.

Estos fueron solo algunos ejemplos. Puede mencionar macros "sin olvidar" como with-open series, ese entorno de limpieza y protegerlo de filtraciones de recursos, nuevas macros de construcciones como cond en lugar de if s, y, por supuesto, no se olvide de construcciones vagas como if, or y and, que no evalúan sus argumentos (en oposición a la aplicación de procedimiento).

Algunos programadores pueden abogar por que su lenguaje tenga una tecnología para tratar este o aquel caso (ORM, AOP, etc.), pero pregúnteles si todas estas tecnologías serían necesarias si existieran macros.

Por lo tanto, tomándolo en conjunto y respondiendo la pregunta original sobre cómo explicar las macros. Tome cualquier código ampliamente usado en Java (C#, C++, etc.), transfórmelo en Lisp y luego vuelva a escribirlo como una macro.

0

Veo las macros como una abstracción similar (o doble) a las funciones, excepto que puede elegir cuándo y cómo evaluar los argumentos. Esto subraya por qué las macros son útiles, al igual que las funciones, para evitar la duplicación de código y facilitar el mantenimiento.

Mi ejemplo favorito son las macros anafóricas. Como FIA, o un tiempo planteamos:

(defmacro aif (test-form then-form &optional else-form) 
    `(let ((it ,test-form)) 
    (if it ,then-form ,else-form))) 

    (defmacro awhile (expr &body body) 
     `(do ((it ,expr ,expr)) ((not it)) 
     ,@body)) 

    (defmacro aand (&rest args) 
     (cond 
     ((null args) t) 
     ((null (cdr args)) (car args)) 
     (t `(aif ,(car args) (aand ,@(cdr args)))))) 

Estos son muy simples y pueden ahorrar mucho escribir.

0

No es algo que pueda explicar en un corto período de tiempo, bueno, el concepto de macro se puede explicar en una oración y un ejemplo como el tiempo es bastante fácil de entender, el problema es que esa persona realmente no entenderá por qué las macros son algo bueno tener solo con ejemplos tan triviales.

3

Dado ejemplos concretos pueden atascarse en los detalles de la lengua que les está escribiendo en, considere una declaración no concreta, pero relacionables:

"Usted sabe que todo ese código repetitivo que a veces se tiene que escribir ? Nunca tienes que escribir repetitivo en lisp, ya que siempre puedes escribir un generador de código para que lo haga por ti ".

Por 'boilerplate', estoy pensando en implementaciones de interfaz únicas en Java, anulando constructores implícitos en C++, escribiendo pares get() - set(), etc. Creo que esta estrategia retórica podría funcionar mejor que intentar para explicar macros directamente con demasiado detalle, ya que probablemente esté demasiado familiarizado con varias formas de repetición, mientras que nunca ha visto una macro.

-1

no, no lo hay. uno debe estar familiarizado con lisp para comprender macros claramente.

Cuestiones relacionadas