programación programa principiantes para lenguajes introduccion estructura ejemplos descargar caracteristicas lisp homoiconicity

programa - lisp pdf



Ejemplo práctico de la flexibilidad de Lisp? (18)

¿Has echado un vistazo a this explicación de por qué las macros son potentes y flexibles? Sin embargo, no hay ejemplos en otros idiomas, lo siento, pero podría venderle en macros.

Alguien está tratando de vender Lisp para mí, como un lenguaje poderoso que puede hacer de todo, y algo más.

¿Hay algún ejemplo de código práctico del poder de Lisp?
(Preferiblemente junto con una lógica equivalente codificada en un lenguaje regular).


@Marca,

Si bien hay algo de verdad en lo que dices, creo que no siempre es tan directo.

Los programadores y las personas en general no siempre se toman el tiempo para evaluar todas las posibilidades y decidir cambiar de idioma. A menudo son los gerentes los que deciden, o las escuelas que enseñan los primeros idiomas ... y los programadores nunca tienen la necesidad de invertir la cantidad de tiempo suficiente para llegar a un cierto nivel si pueden decidir que este idioma me ahorra más tiempo que ese idioma.

Además, debes admitir que los idiomas que cuentan con el respaldo de grandes entidades comerciales como Microsoft o Sun siempre tendrán una ventaja en el mercado en comparación con los idiomas que no cuentan con dicho respaldo.

Para responder la pregunta original, Paul Graham intenta dar un ejemplo here , aunque admito que no es necesariamente tan práctico como me gustaría :-)


El mejor ejemplo que puedo pensar que está ampliamente disponible es el libro de Paul Graham, On Lisp . El PDF completo se puede descargar desde el enlace que acabo de dar. También puede probar Practical Common Lisp (también disponible por completo en la web).

Tengo muchos ejemplos poco prácticos. Una vez escribí un programa en aproximadamente 40 líneas de lisp que podía analizarse, tratar su fuente como una lista de lisp, hacer un recorrido de árbol de la lista y construir una expresión que evaluara a WALDO si el identificador de waldo existía en la fuente o evaluar a nil si waldo no estaba presente. La expresión devuelta se construyó añadiendo llamadas a car / cdr a la fuente original que se analizó. No tengo idea de cómo hacer esto en otros idiomas en 40 líneas de código. Quizás Perl puede hacerlo incluso en menos líneas.


El simple hecho de que es un lenguaje multi-paradigma lo hace muy muy flexible.


En realidad, un buen ejemplo práctico es la macro Lisp LOOP.

http://www.ai.sri.com/pkarp/loop.html

La macro LOOP es simplemente eso - una macro Lisp. Sin embargo, básicamente define un DSL de mini bucle (lenguaje específico del dominio).

Cuando navega por ese pequeño tutorial, puede ver (incluso como novato) que es difícil saber qué parte del código es parte de la macro Loop, y cuál es Lisp "normal".

Y ese es uno de los componentes clave de la expresividad Lisp, que el nuevo código realmente no se puede distinguir del sistema.

Si bien, por ejemplo, en Java, no puede (a simple vista) saber qué parte de un programa proviene de la biblioteca estándar de Java versus su propio código, o incluso una biblioteca de terceros, usted sabe qué parte del código es el lenguaje Java en lugar de simplemente llamadas de método en las clases. De acuerdo, es TODO el "lenguaje Java", pero como programador, estás limitado a expresar tu aplicación como una combinación de clases y métodos (y ahora, anotaciones). Mientras que en Lisp, literalmente, todo está en juego.

Considere la interfaz Common SQL para conectar Common Lisp a SQL. Aquí, http://clsql.b9.com/manual/loop-tuples.html , muestran cómo se extiende la macro CL Loop para hacer que el SQL sea un "ciudadano de primera clase".

También puede observar construcciones como "[seleccione [primer nombre] [apellido]: de [empleado]: orden-por [apellido]]". Esto es parte del paquete CL-SQL y se implementa como una "macro lector".

Mira, en Lisp, no solo puedes hacer macros para crear nuevas construcciones, como estructuras de datos, estructuras de control, etc. Pero incluso puedes cambiar la sintaxis del lenguaje a través de una macro de lector. Aquí, están usando una macro de lector (en el caso, el ''['' símbolo) para pasar a un modo SQL para hacer que SQL funcione como SQL incorporado, en lugar de simplemente como cadenas sin formato, como en muchos otros lenguajes.

Como desarrolladores de aplicaciones, nuestra tarea es convertir nuestros procesos y construcciones en una forma que el procesador pueda entender. Eso significa que, inevitablemente, tenemos que "hablar mal" con el lenguaje informático, ya que "no nos entiende".

Common Lisp es uno de los pocos entornos en los que no solo podemos construir nuestra aplicación de arriba hacia abajo, sino dónde podemos elevar el idioma y el entorno para alcanzarnos a mitad de camino. Podemos codificar en ambos extremos.

Mente, tan elegante como esto puede ser, no es una panacea. Obviamente, hay otros factores que influyen en la elección del idioma y el entorno. Pero sin duda vale la pena aprender y jugar. Creo que aprender Lisp es una excelente manera de avanzar en tu programación, incluso en otros idiomas.


Encontré este artículo bastante interesante:

Comparación del lenguaje de programación: Lisp vs C ++

El autor del artículo, Brandon Corfman, escribe sobre un estudio que compara soluciones en Java, C ++ y Lisp con un problema de programación, y luego escribe su propia solución en C ++. La solución de referencia son las 45 líneas de Lisp de Peter Norvig (escritas en 2 horas).

Corfman encuentra que es difícil reducir su solución a menos de 142 líneas de C ++ / STL. Su análisis de por qué, es una lectura interesante.


Fui estudiante de AI en el MIT en la década de 1970. Como cualquier otro estudiante, pensé que el lenguaje era primordial. Sin embargo, Lisp fue el idioma principal. Estas son algunas cosas por las que todavía creo que es bastante bueno:

  • Matemática simbólica Es fácil e instructivo escribir la diferenciación simbólica de una expresión y la simplificación algebraica. Todavía los hago, aunque los hago en C-lo que sea.

  • Teorema de prueba. De vez en cuando me embarco en un atracón temporal de IA, como intentar demostrar que el tipo de inserción es correcto. Para eso necesito hacer una manipulación simbólica, y generalmente recurro a Lisp.

  • Pequeños lenguajes específicos de dominio. Sé que Lisp no es realmente práctico, pero si quiero probar un poco de DSLs sin tener que involucrarme en el análisis sintáctico, etc., las macros Lisp lo hacen fácil.

  • Los algoritmos de Little Play como la búsqueda de árbol de juego de minimax se pueden hacer en tres líneas.

  • ¿Quieres probar el cálculo lambda ? Es fácil en Lisp.

Principalmente, lo que Lisp hace por mí es ejercicio mental. Entonces puedo llevar eso a más idiomas prácticos.

PD: Hablando de cálculo lambda, lo que también comenzó en la década de 1970, en ese mismo entorno de AI, fue que OO comenzó a invadir el cerebro de todos, y de alguna manera, el interés en lo que parece ha desplazado mucho interés en lo que es bueno . Es decir, el trabajo sobre aprendizaje automático, lenguaje natural, visión, resolución de problemas, todo tipo de fue al fondo de la sala, mientras que las clases, los mensajes, los tipos, el polimorfismo, etc. pasaron al frente.


Hay muchas características increíbles en Lisp, pero macros es una de las que amo particularmente, porque ya no existe una barrera entre lo que el lenguaje define y lo que defino. Por ejemplo, Common Lisp no tiene una construcción while . Una vez lo implementé en mi cabeza, mientras caminaba. Es sencillo y limpio:

(defmacro while (condition &body body) `(if ,condition (progn ,@body (do nil ((not ,condition)) ,@body))))

Et voilà! Acaba de extender el lenguaje Common Lisp con una nueva construcción fundamental. Ahora puede hacer:

(let ((foo 5)) (while (not (zerop (decf foo))) (format t "still not zero: ~a~%" foo)))

Que imprimiría:

still not zero: 4 still not zero: 3 still not zero: 2 still not zero: 1

Hacer eso en cualquier idioma que no sea Lisp se deja como un ejercicio para el lector ...


John Ousterhout hizo esta observación interesante con respecto a Lisp en 1994:

Los diseñadores de idiomas adoran discutir sobre por qué este lenguaje o ese lenguaje debe ser mejor o peor a priori, pero ninguno de estos argumentos realmente importa mucho. En última instancia, todos los problemas de idioma se resuelven cuando los usuarios votan con los pies.

Si [un idioma] hace que las personas sean más productivas, entonces lo usarán; cuando aparezca algún otro idioma que sea mejor (o si ya está aquí), las personas cambiarán a ese idioma. Esta es La Ley, y es bueno. La ley me dice que Scheme (o cualquier otro dialecto de Lisp) probablemente no sea el idioma "correcto": demasiadas personas han votado con los pies en los últimos 30 años.

http://www.vanderburg.org/OldPages/Tcl/war/0009.html


Lo que más me gusta de los sistemas Lisp (y Smalltalk ) es que se sienten vivos. Puede probar y modificar fácilmente los sistemas Lisp mientras se están ejecutando.

Si esto suena misterioso, inicie Emacs y escriba algún código Lisp. Escribe CMx y voilà! Acabas de cambiar Emacs desde dentro de Emacs. Puede continuar y redefinir todas las funciones de Emacs mientras se está ejecutando.

Otra cosa es que la equivalencia code = list hace que la frontera entre código y datos sea muy delgada. Y gracias a las macros, es muy fácil ampliar el idioma y hacer DSLs rápidas.

Por ejemplo, es posible codificar un generador de HTML básico con el cual el código está muy cerca de la salida HTML producida:

(html (head (title "The Title")) (body (h1 "The Headline" :class "headline") (p "Some text here" :id "content")))

=>

<html> <head> <title>The title</title> </head> <body> <h1 class="headline">The Headline</h1> <p id="contents">Some text here</p> </body> </html>

En el código Lisp, la sangría automática hace que el código se vea como el resultado, excepto que no hay ninguna etiqueta de cierre.


Me gusta Common Lisp Object System (CLOS) y multimétodos.

La mayoría, si no todos, los lenguajes de programación orientados a objetos tienen las nociones básicas de clases y métodos. El siguiente fragmento en Python define las clases PeelingTool y Vegetable (algo similar al patrón Visitor):

class PeelingTool: """I''m used to peel things. Mostly fruit, but anything peelable goes.""" def peel(self, veggie): veggie.get_peeled(self) class Veggie: """I''m a defenseless Veggie. I obey the get_peeled protocol used by the PeelingTool""" def get_peeled(self, tool): pass class FingerTool(PeelingTool): ... class KnifeTool(PeelingTool): ... class Banana(Veggie): def get_peeled(self, tool): if type(tool) == FingerTool: self.hold_and_peel(tool) elif type(tool) == KnifeTool: self.cut_in_half(tool)

Pones el método peel en PeelingTool y haces que el Banana lo acepte. Pero debe pertenecer a la clase PeelingTool, por lo que solo se puede usar si tiene una instancia de la clase PeelingTool.

La versión del Common Lisp Object System:

(defclass peeling-tool () ()) (defclass knife-tool (peeling-tool) ()) (defclass finger-tool (peeling-tool) ()) (defclass veggie () ()) (defclass banana (veggie) ()) (defgeneric peel (veggie tool) (:documentation "I peel veggies, or actually anything that wants to be peeled")) ;; It might be possible to peel any object using any tool, ;; but I have no idea how. Left as an exercise for the reader (defmethod peel (veggie tool) ...) ;; Bananas are easy to peel with our fingers! (defmethod peel ((veggie banana) (tool finger-tool)) (with-hands (left-hand right-hand) *me* (hold-object left-hand banana) (peel-with-fingers right-hand tool banana))) ;; Slightly different using a knife (defmethod peel ((veggie banana) (tool knife-tool)) (with-hands (left-hand right-hand) *me* (hold-object left-hand banana) (cut-in-half tool banana)))

Cualquier cosa puede escribirse en cualquier idioma que esté completo; la diferencia entre los idiomas es cuántos aros tienes que saltar para obtener el resultado equivalente.

Los lenguajes potentes como Common Lisp , con funcionalidades como macros y CLOS, le permiten obtener resultados de manera rápida y fácil sin saltos de tantos aros que puede conformarse con una solución insatisfactoria o convertirse en un canguro.


Me gusta este ejemplo macro de http://common-lisp.net/cgi-bin/viewcvs.cgi/cl-selenium/?root=cl-selenium Es un enlace Common Lisp para Selenium (un marco de prueba de navegador web), pero en lugar de asignar cada método, lee el documento XML de definición de API de Selenium en tiempo de compilación y genera el código de asignación mediante macros. Aquí puede ver la API generada: common-lisp.net/project/cl-selenium/api/selenium-package/index.html

Esto es, esencialmente, manejar macros con datos externos, que en este caso es un documento XML, pero podría haber sido tan complejo como leer desde una base de datos o red. Este es el poder de tener todo el entorno Lisp a su disposición en el momento de la compilación.


Me gustan los macros

Aquí hay un código para eliminar los atributos de las personas de LDAP. Me sucedió que tenía ese código por ahí y me pareció que sería útil para otros.

Algunas personas están confundidas por una supuesta penalización en tiempo de ejecución de las macros, así que he agregado un intento de aclarar las cosas al final.

En el comienzo, hubo duplicación

(defun ldap-users () (let ((people (make-hash-table :test ''equal))) (ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))")) (let ((mail (car (ldap:attr-value ent ''mail))) (uid (car (ldap:attr-value ent ''uid))) (name (car (ldap:attr-value ent ''cn))) (phonenumber (car (ldap:attr-value ent ''telephonenumber)))) (setf (gethash uid people) (list mail name phonenumber)))) people))

Puede pensar en un "dejar vinculante" como una variable local, que desaparece fuera del formulario LET. Observe la forma de los enlaces: son muy similares, y difieren solo en el atributo de la entidad LDAP y el nombre ("variable local") para vincular el valor. Útil, pero un poco detallado y contiene duplicación.

En busca de la belleza

Ahora, ¿no sería bueno si no tuviéramos que tener toda esa duplicación? Una expresión común es WITH -... macros, que une valores basados ​​en una expresión de la que puede tomar los valores. Presentemos nuestra propia macro que funciona de esa manera, WITH-LDAP-ATTRS, y reemplácela en nuestro código original.

(defun ldap-users () (let ((people (make-hash-table :test ''equal))) ; equal so strings compare equal! (ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))")) (with-ldap-attrs (mail uid name phonenumber) ent (setf (gethash uid people) (list mail name phonenumber)))) people))

¿Viste cómo un grupo de líneas desapareció repentinamente y fue reemplazado por una sola línea? ¿Como hacer esto? Usando macros, por supuesto, ¡código que escribe código! Macros en Lisp es un animal totalmente diferente de los que puedes encontrar en C / C ++ mediante el uso del pre-procesador: aquí puedes ejecutar código Lisp real (no el #define pelusa en cpp) que genera el código Lisp, antes el otro código está compilado. Las macros pueden usar cualquier código Lisp real, es decir, funciones ordinarias. Esencialmente sin límites.

Deshacerse de lo feo

Entonces, veamos cómo se hizo esto. Para reemplazar un atributo, definimos una función.

(defun ldap-attr (entity attr) `(,attr (car (ldap:attr-value ,entity '',attr))))

La sintaxis de backquote parece un poco peluda, pero lo que hace es fácil. Cuando llames a LDAP-ATTRS, escupirá una lista que contiene el valor de attr (que es la coma), seguido por car ("primer elemento en la lista" (contra par, en realidad), y de hecho hay una función llamada first , puede usar también), que recibe el primer valor en la lista devuelta por ldap:attr-value . Debido a que este no es el código que queremos ejecutar cuando compilamos el código (obtener los valores de los atributos es lo que queremos hacer cuando ejecutamos el programa), no agregamos una coma antes de la llamada.

De todas formas. Avanzando, al resto de la macro.

(defmacro with-ldap-attrs (attrs ent &rest body) `(let ,(loop for attr in attrs collecting `,(ldap-attr ent attr)) ,@body))

El ,@ -syntax es poner el contenido de una lista en algún lugar, en lugar de la lista real.

Resultado

Puede verificar fácilmente que esto le dará lo correcto. Las macros a menudo se escriben de esta manera: empiezas con el código que deseas simplificar (la salida), lo que quieres escribir en su lugar (la entrada) y luego comienzas a moldear la macro hasta que tu entrada da la salida correcta. La función macroexpand-1 le dirá si su macro es correcta:

(macroexpand-1 ''(with-ldap-attrs (mail phonenumber) ent (format t "~a with ~a" mail phonenumber)))

evalúa a

(let ((mail (car (trivial-ldap:attr-value ent ''mail))) (phonenumber (car (trivial-ldap:attr-value ent ''phonenumber)))) (format t "~a with ~a" mail phonenumber))

Si comparas los enlaces LET de la macro expandida con el código al principio, ¡encontrarás que está en la misma forma!

Tiempo de compilación vs tiempo de ejecución: macros vs funciones

Una macro es un código que se ejecuta en tiempo de compilación , con el giro adicional de que pueden llamar a cualquier función ordinaria o macro a su gusto. No es mucho más que un filtro de fantasía, toma algunos argumentos, aplica algunas transformaciones y luego le proporciona al compilador las s-exps resultantes.

Básicamente, le permite escribir su código en verbos que se pueden encontrar en el dominio del problema, en lugar de primitivos de bajo nivel del idioma. Como un ejemplo tonto, considere lo siguiente (si es que aún no estaba incorporado) ::

(defmacro my-when (test &rest body) `(if ,test (progn ,@body)))

if es una primitiva incorporada que solo le permitirá ejecutar un formulario en las ramas, y si quiere tener más de uno, bueno, debe usar progn ::

;; one form (if (numberp 1) (print "yay, a number")) ;; two forms (if (numberp 1) (progn (assert-world-is-sane t) (print "phew!"))))

Con nuestro nuevo amigo, my-when , ambos podríamos a) usar el verbo más apropiado si no tenemos una bifurcación falsa, yb) agregar un operador de secuenciamiento implícito, es decir, progn ::

(my-when (numberp 1) (assert-world-is-sane t) (print "phew!"))

El código compilado nunca contendrá my-when , porque, en el primer paso, todas las macros se expanden por lo que no hay penalización en tiempo de ejecución .

Lisp> (macroexpand-1 ''(my-when (numberp 1) (print "yay!"))) (if (numberp 1) (progn (print "yay!")))

Tenga en cuenta que macroexpand-1 solo hace un nivel de expansiones; es posible (¡muy probablemente, de hecho!) que la expansión continúe más abajo. Sin embargo, eventualmente llegarás a los detalles de implementación específicos del compilador que a menudo no son muy interesantes. Pero continuar ampliando el resultado eventualmente le dará más detalles, o solo su entrada s-exp hacia atrás.

Espero que aclare las cosas. Macros es una herramienta poderosa, y una de las características en Lisp que me gusta.


Puede encontrar útil esta publicación de Eric Normand. Describe cómo a medida que crece una base de código, Lisp ayuda al permitirle crear el lenguaje para su aplicación. Si bien esto a menudo requiere un esfuerzo adicional desde el principio, te brinda una gran ventaja más adelante.


Puede encontrar útil este artículo: http://www.defmacro.org/ramblings/lisp.html

Dicho esto, es muy, muy difícil dar ejemplos cortos y prácticos del poder de Lisp porque realmente solo brilla en un código no trivial. Cuando su proyecto crezca hasta cierto tamaño, apreciará las instalaciones de abstracción de Lisp y se alegrará de que las haya estado usando. Las muestras de código razonablemente cortas, por otro lado, nunca le darán una demostración satisfactoria de lo que hace que Lisp sea genial porque las abreviaturas predefinidas de otros idiomas se verán más atractivas en pequeños ejemplos que la flexibilidad de Lisp en la administración de abstracciones específicas de dominio.


Una cosa específica que me impresionó es la capacidad de escribir tu propia extensión de programación orientada a objetos, si no te gusta el CLOS incluido.

Uno de ellos está en Garnet , y uno en On Lisp de Paul Graham.

También hay un paquete llamado Screamer que permite la programación no determinista (que no he evaluado).

Cualquier lenguaje que le permita cambiarlo para soportar diferentes paradigmas de programación tiene que ser flexible.


Una cosa que me gusta es el hecho de que puedo actualizar el código "run-time" sin perder el estado de la aplicación. Es algo que solo es útil en algunos casos, pero cuando es útil, tenerlo ya allí (o, por solo un costo mínimo durante el desarrollo) es MUCHO más económico que tener que implementarlo desde cero. Especialmente porque esto tiene un costo de "no casi nada".


Vea cómo puede extender Common Lisp con plantillas XML : ejemplo XML de cl-quasi-quote , página de proyecto ,

(babel:octets-to-string (with-output-to-sequence (*html-stream*) <div (constantAttribute 42 someJavaScript `js-inline(print (+ 40 2)) runtimeAttribute ,(concatenate ''string "&foo" "&bar")) <someRandomElement <someOther>>>)) => "<div constantAttribute=/"42/" someJavaScript=/"javascript: print((40 + 2))/" runtimeAttribute=/"&amp;foo&amp;bar/"> <someRandomElement> <someOther/> </someRandomElement> </div>"

Esto es básicamente lo mismo que el lector backtick de Lisp (que es para la lista cuasi cotización), pero también funciona para otras cosas como XML (instalado en una sintaxis <> especial), JavaScript (instalado en `js-inline), etc. .

Para dejarlo en claro, esto se implementa en una biblioteca de usuario . Y compila las partes estáticas de XML, JavaScript, etc. en matrices de bytes literales codificadas en UTF-8 que están listas para escribirse en la secuencia de la red. Con un simple , (coma) puede volver a lisp e intercalar datos generados en tiempo de ejecución en las matrices de bytes literales.

Esto no es para corazones débiles, pero esto es lo que la biblioteca compila en lo anterior en:

(progn (write-sequence #(60 100 105 118 32 99 111 110 115 116 97 110 116 65 116 116 114 105 98 117 116 101 61 34 52 50 34 32 115 111 109 101 74 97 118 97 83 99 114 105 112 116 61 34 106 97 118 97 115 99 114 105 112 116 58 32 112 114 105 110 116 40 40 52 48 32 43 32 50 41 41 34 32 114 117 110 116 105 109 101 65 116 116 114 105 98 117 116 101 61 34) *html-stream*) (write-quasi-quoted-binary (let ((*transformation* #<quasi-quoted-string-to-quasi-quoted-binary {1006321441}>)) (transform-quasi-quoted-string-to-quasi-quoted-binary (let ((*transformation* #<quasi-quoted-xml-to-quasi-quoted-string {1006326E51}>)) (locally (declare (sb-ext:muffle-conditions sb-ext:compiler-note)) (let ((it (concatenate ''string "runtime calculated: " "&foo" "&bar"))) (if it (transform-quasi-quoted-xml-to-quasi-quoted-string/attribute-value it) nil)))))) *html-stream*) (write-sequence #(34 62 10 32 32 60 115 111 109 101 82 97 110 100 111 109 69 108 101 109 101 110 116 62 10 32 32 32 32 60 115 111 109 101 79 116 104 101 114 47 62 10 32 32 60 47 115 111 109 101 82 97 110 100 111 109 69 108 101 109 101 110 116 62 10 60 47 100 105 118 62 10) *html-stream*) +void+)

Como referencia, los dos vectores de gran byte en el ejemplo anterior se ven así cuando se convierten en cadena:

"<div constantAttribute=/"42/" someJavaScript=/"javascript: print((40 + 2))/" runtimeAttribute=/""

Y el segundo:

"/"> <someRandomElement> <someOther/> </someRandomElement> </div>"

Y se combina bien con otras estructuras Lisp como macros y funciones. ahora, compara esto con las JSPs ...