Al leer los ensayos de Paul Graham sobre lenguajes de programación, uno pensaría que las macros de Lisp son el único camino a seguir. Como desarrollador ocupado, trabajando en otras plataformas, no he tenido el privilegio de usar macros Lisp. Como alguien que quiere entender el rumor, explique qué hace que esta característica sea tan poderosa.
También relacione esto con algo que entendería de los mundos del desarrollo de Python, Java, C # o C.
macros
lisp
homoiconicity
menta
fuente
fuente
Respuestas:
Para dar una respuesta breve, las macros se utilizan para definir extensiones de sintaxis de lenguaje a Common Lisp o Lenguajes específicos de dominio (DSL). Estos idiomas están integrados directamente en el código Lisp existente. Ahora, los DSL pueden tener una sintaxis similar a Lisp (como el Prolog Interpreter de Peter Norvig para Common Lisp) o completamente diferente (por ejemplo, Infix Notation Math para Clojure).
Aquí hay un ejemplo más concreto:
Python tiene comprensiones de listas integradas en el lenguaje. Esto proporciona una sintaxis simple para un caso común. La línea
produce una lista que contiene todos los números pares entre 0 y 9. De vuelta en Python 1.5 días no había tal sintaxis; usarías algo más como esto:
Ambos son funcionalmente equivalentes. Invoquemos nuestra suspensión de la incredulidad y pretendamos que Lisp tiene una macro de bucle muy limitada que solo hace iteraciones y no es una manera fácil de hacer el equivalente de las comprensiones de listas.
En Lisp podrías escribir lo siguiente. Debo señalar que este ejemplo artificial se elige para ser idéntico al código de Python, no es un buen ejemplo de código Lisp.
Antes de continuar, debería explicar mejor qué es una macro. Es una transformación realizada en código por código. Es decir, un fragmento de código, leído por el intérprete (o compilador), que toma el código como argumento, manipula y devuelve el resultado, que luego se ejecuta in situ.
Por supuesto, eso es mucho escribir y los programadores son flojos. Entonces podríamos definir DSL para hacer comprensiones de listas. De hecho, ya estamos usando una macro (la macro de bucle).
Lisp define un par de formas de sintaxis especiales. La cita (
'
) indica que el siguiente token es literal. El cuasiquote o backtick (`
) indica que el siguiente token es un literal con escapes. Los escapes son indicados por el operador de coma. El literal'(1 2 3)
es el equivalente de Python[1, 2, 3]
. Puede asignarlo a otra variable o usarlo en su lugar. Puede pensar`(1 2 ,x)
como el equivalente de Python[1, 2, x]
dondex
es una variable previamente definida. Esta notación de la lista es parte de la magia que entra en las macros. La segunda parte es el lector Lisp que sustituye inteligentemente las macros por el código, pero que se ilustra mejor a continuación:Entonces podemos definir una macro llamada
lcomp
(abreviatura para la comprensión de la lista). Su sintaxis será exactamente como la pitón que usamos en el ejemplo[x for x in range(10) if x % 2 == 0]
:(lcomp x for x in (range 10) if (= (% x 2) 0))
Ahora podemos ejecutar en la línea de comando:
Bastante ordenado, ¿eh? Ahora no se detiene ahí. Tienes un mecanismo, o un pincel, si quieres. Puede tener cualquier sintaxis que pueda desear. Como Python o la
with
sintaxis de C # . O la sintaxis LINQ de .NET. Al final, esto es lo que atrae a las personas a Lisp: la máxima flexibilidad.fuente
(loop for x from 0 below 10 when (evenp x) collect x)
, más ejemplos aquí . Pero, de hecho, el bucle es "solo una macro" (en realidad lo volví a implementar desde cero hace algún tiempo )Encontrarás un debate exhaustivo sobre lisp macro aquí .
Un subconjunto interesante de ese artículo:
fuente
Las macros comunes de Lisp esencialmente extienden las "primitivas sintácticas" de su código.
Por ejemplo, en C, la construcción de cambio / caso solo funciona con tipos integrales y si desea usarla para flotantes o cadenas, le quedan instrucciones anidadas if y comparaciones explícitas. Tampoco hay forma de que pueda escribir una macro C para hacer el trabajo por usted.
Pero, dado que una macro lisp es (esencialmente) un programa lisp que toma fragmentos de código como entrada y devuelve código para reemplazar la "invocación" de la macro, puede extender su repertorio "primitivo" todo lo que desee, generalmente terminando con un programa más legible.
Para hacer lo mismo en C, tendría que escribir un preprocesador personalizado que comiera su fuente inicial (no bastante C) y escupe algo que un compilador de C puede entender. No es una forma incorrecta de hacerlo, pero no es necesariamente la más fácil.
fuente
Las macros Lisp le permiten decidir cuándo (si es que lo hace) alguna parte o expresión será evaluada. Para poner un ejemplo simple, piense en las C:
Lo que esto dice es: evaluar
expr1
, y, en caso de ser cierto, evaluarexpr2
, etc.Ahora intenta convertir esto
&&
en una función ... así es, no puedes. Llamando algo como:¡Evaluará los tres
exprs
antes de dar una respuesta, independientemente de siexpr1
fue falsa!Con macros lisp puedes codificar algo como:
ahora tiene un
&&
, que puede llamar como una función y no evaluará ningún formulario que le pase a menos que todos sean verdaderos.Para ver cómo esto es útil, contraste:
y:
Otras cosas que puede hacer con las macros son crear nuevas palabras clave y / o mini idiomas (consulte la
(loop ...)
macro para ver un ejemplo), integrando otros idiomas en lisp, por ejemplo, podría escribir una macro que le permita decir algo como:Y eso ni siquiera es entrar en las macros de Reader .
Espero que esto ayude.
fuente
(and ...
evaluará las expresiones hasta que se evalúe como falso, tenga en cuenta que se producirán los efectos secundarios generados por la evaluación falsa, solo se omitirán las expresiones posteriores.No creo haber visto las macros de Lisp mejor explicadas que este tipo: http://www.defmacro.org/ramblings/lisp.html
fuente
Piense en lo que puede hacer en C o C ++ con macros y plantillas. Son herramientas muy útiles para administrar código repetitivo, pero están limitadas de manera bastante severa.
Las macros Lisp y Lisp resuelven estos problemas.
Hable con cualquiera que domine C ++ y pregúnteles cuánto tiempo pasaron aprendiendo toda la falsificación de plantillas que necesitan para realizar la metaprogramación de plantillas. O todos los trucos locos en libros (excelentes) como Modern C ++ Design , que aún son difíciles de depurar y (en la práctica) no portátiles entre compiladores del mundo real a pesar de que el lenguaje se ha estandarizado durante una década. ¡Todo eso se desvanece si el lenguaje que usa para la metaprogramación es el mismo lenguaje que usa para la programación!
fuente
Una macro lisp toma un fragmento de programa como entrada. Este fragmento de programa se representa como una estructura de datos que se puede manipular y transformar de la forma que desee. Al final, la macro genera otro fragmento de programa, y este fragmento es lo que se ejecuta en tiempo de ejecución.
C # no tiene una función de macro, sin embargo, un equivalente sería si el compilador analizara el código en un árbol de CodeDOM y lo pasara a un método, que lo transformó en otro CodeDOM, que luego se compila en IL.
Esto podría usarse para implementar la sintaxis "azúcar" como la
for each
declaraciónusing
-clause, linq -expressions,select
etc., como macros que se transforman en el código subyacente.Si Java tuviera macros, podría implementar la sintaxis de Linq en Java, sin necesidad de que Sun cambie el idioma base.
Aquí hay un seudocódigo para ver cómo
using
podría verse una macro de estilo lisp en C # para implementar :fuente
using
se vería así ;)Dado que las respuestas existentes brindan buenos ejemplos concretos que explican qué logran las macros y cómo, tal vez sería útil reunir algunas de las ideas sobre por qué la instalación macro es una ganancia significativa en relación con otros idiomas ; primero de estas respuestas, luego una excelente de otro lado:
- Vatine
- Matt Curtis
- Miguel Ping
- Peter Seibel, en "Practical Common Lisp"
fuente
No estoy seguro de poder agregar una idea a las publicaciones (excelentes) de todos, pero ...
Las macros de Lisp funcionan muy bien debido a la naturaleza de sintaxis de Lisp.
Lisp es un lenguaje extremadamente regular (piense que todo es una lista ); Las macros le permiten tratar los datos y el código de la misma manera (no se necesitan análisis de cadenas u otros hacks para modificar expresiones lisp). Combina estas dos características y tiene una forma muy limpia de modificar el código.
Editar: Lo que estaba tratando de decir es que Lisp es homoicónico , lo que significa que la estructura de datos para un programa lisp está escrita en el mismo lisp.
Entonces, terminas con una forma de crear tu propio generador de código en la parte superior del lenguaje usando el lenguaje en sí con toda su potencia (por ejemplo, en Java tienes que hackear tu camino con el tejido de código de bytes, aunque algunos marcos como AspectJ te permiten hacer esto usando un enfoque diferente, es fundamentalmente un truco).
En la práctica, con las macros terminas construyendo tu propio mini-idioma además de lisp, sin la necesidad de aprender idiomas o herramientas adicionales, y con el uso de todo el poder del lenguaje en sí.
fuente
S-expressions
, no listas.Las macros de Lisp representan un patrón que ocurre en casi cualquier proyecto de programación considerable. Eventualmente, en un programa grande, tiene una cierta sección de código donde se da cuenta de que sería más simple y menos propenso a errores escribir un programa que genere código fuente como texto que luego puede pegar.
En Python los objetos tienen dos métodos
__repr__
y__str__
.__str__
es simplemente la representación humana legible.__repr__
devuelve una representación que es código Python válido, es decir, algo que se puede ingresar en el intérprete como Python válido. De esta manera, puede crear pequeños fragmentos de Python que generen código válido que se pueda pegar en su fuente real.En Lisp todo este proceso ha sido formalizado por el sistema macro. Claro que le permite crear extensiones a la sintaxis y hacer todo tipo de cosas elegantes, pero su utilidad real se resume en lo anterior. Por supuesto, ayuda que el sistema macro Lisp le permita manipular estos "fragmentos" con toda la potencia de todo el lenguaje.
fuente
En resumen, las macros son transformaciones de código. Permiten introducir muchas nuevas construcciones de sintaxis. Por ejemplo, considere LINQ en C #. En lisp, hay extensiones de lenguaje similares implementadas por macros (por ejemplo, construcción de bucle incorporada, iteración). Las macros disminuyen significativamente la duplicación de código. Las macros permiten incrustar «pequeños lenguajes» (p. Ej., Donde en c # / java se usaría xml para configurar, en lisp se puede lograr lo mismo con las macros). Las macros pueden ocultar dificultades de uso de bibliotecas.
Por ejemplo, en lisp puedes escribir
y esto oculta todas las cosas de la base de datos (transacciones, cierre de conexión adecuado, obtención de datos, etc.) mientras que en C # esto requiere crear SqlConnections, SqlCommands, agregar SqlParameters a SqlCommands, hacer un bucle en SqlDataReaders, cerrarlos correctamente.
fuente
Si bien todo lo anterior explica qué son las macros e incluso tienen ejemplos geniales, creo que la diferencia clave entre una macro y una función normal es que LISP evalúa todos los parámetros primero antes de llamar a la función. Con una macro es al revés, LISP pasa los parámetros sin evaluar a la macro. Por ejemplo, si pasa (+ 1 2) a una función, la función recibirá el valor 3. Si pasa esto a una macro, recibirá una Lista (+ 1 2). Esto se puede usar para hacer todo tipo de cosas increíblemente útiles.
Mida el tiempo que lleva ejecutar una función. Con una función, el parámetro se evaluaría antes de pasar el control a la función. Con la macro, puede empalmar su código entre el inicio y la detención de su cronómetro. El siguiente tiene exactamente el mismo código en una macro y una función y el resultado es muy diferente. Nota: Este es un ejemplo artificial y la implementación se eligió de modo que sea idéntica para resaltar mejor la diferencia.
fuente
Obtuve esto del libro de cocina común de lisp, pero creo que explica por qué las macros de lisp son buenas de una manera agradable.
"Una macro es una pieza ordinaria de código Lisp que opera en otra pieza de código Lisp putativo, traduciéndolo a (una versión más cercana a) Lisp ejecutable. Eso puede sonar un poco complicado, así que vamos a dar un ejemplo simple. Supongamos que quiere un versión de setq que establece dos variables en el mismo valor. Entonces, si escribe
cuando
z=8
tanto x como y se establecen en 11. (No se me ocurre ningún uso para esto, pero es solo un ejemplo).Debería ser obvio que no podemos definir setq2 como una función. Si
x=50
yy=-5
, esta función recibiría los valores 50, -5 y 11; no tendría conocimiento de qué variables se suponía que debían establecerse. Lo que realmente queremos decir es que, cuando (el sistema Lisp) ve(setq2 v1 v2 e)
, lo trata como equivalente a(progn (setq v1 e) (setq v2 e))
. En realidad, esto no está del todo bien, pero lo hará por ahora. Una macro nos permite hacer precisamente esto, al especificar un programa para transformar el patrón de entrada(setq2 v1 v2 e)
"en el patrón de salida(progn ...)
".Si pensabas que esto era bueno, puedes seguir leyendo aquí: http://cl-cookbook.sourceforge.net/macros.html
fuente
setq2
como una función six
yy
se pasan por referencia. Sin embargo, no sé si es posible en CL. Entonces, para alguien que no conoce a Lisps o CL en particular, este no es un ejemplo muy ilustrativo de la OMIEn python tienes decoradores, básicamente tienes una función que toma otra función como entrada. Puede hacer lo que quiera: llamar a la función, hacer otra cosa, envolver la llamada a la función en una versión de adquisición de recursos, etc., pero no puede mirar dentro de esa función. Digamos que queríamos hacerlo más poderoso, digamos que su decorador recibió el código de la función como una lista, entonces no solo podría ejecutar la función tal como está sino que ahora puede ejecutar partes de ella, reordenar líneas de la función, etc.
fuente