LISP se encuentra entre los más antiguos lenguajes de programación de alto nivel aún en uso generalizado. Fue desarrollado alrededor de 1958 por John McCarthy. La idea de LISP surgió a partir de un sistema lógico llamado "lambda calculus'' desarrollado por Alonzo Church. Existen diversas variantes (o dialectos) de LISP, entre las cuales se encuentran Scheme, T, etc. LISP llegó a ser fundamental como lenguaje de programación para las investigaciones de Inteligencia Artificial, y sigue aún hoy siendo uno de los más utilizados en este campo. En la década de los '80 se intentó estandardizar el lenguaje. Como resultado surgió el Common LISP cyas especificaciones se recogen en Common LISP: The Language, 2nd Edition (CLTL2). Common LISP es actualmente el dialecto más difundido y la base para el desarrollo de numerosas implementaciones.
Las razones para ello se encuentran en el hecho de poseer una de las formas de sintaxis menos restrictivas entre los lenguajes de alto nivel. Esto facilita su aprendizaje, al ser muy corto el número de estructuras y funciones que el estudiante debe conocer para llegar a dominar las técnicas de programación en este lenguaje. De hecho, este curso se propone la utilización de un subconjunto de las muchas funciones disponibles para con ellas examinar las técnicas que hacen de LISP un lenguaje tan especial.
LISP: PARADIGMA DEL ESTILO DE PROGRAMACIÓN FUNCIONAL
Una de Las características de LISP es la posibilidad de tratar las propias funciones como datos. En LISP, funciones e incluso programas enteros pueden ser utilizados directamente como entrada a otros programas o subrutinas. En esto el prototipo para la concepción del lenguaje ha sido la estructura de las funciones matemáticas. Todos sabemos cómo resolver una expresión del tipo (8 * ((17 + 3) / 4)). Primero hallaríamos el resultado de 17 + 3, que entonces dividiríamos entre 4, para el resultado multiplicarlo por 8. Es decir, que iríamos resolviendo los paréntesis más interiores y pasando los resultados a las operaciones descritas en los paréntesis que los contienen.
(* 8 (/ (+ 3 17) 4))
sería la función LISP equivalente.
*, / y + son nombres de funciones LISP. Los números en (+ 3 17)
son
los argumentos que se pasan a la función '+
'. Pero en (/ (+ 3 17) 4)
a
la función '/
' se le está pasando un argumento numérico 4,
pero también (+ 3 17), otra función con dos argumentos
numéricos. Esta es la esencia de un lenguaje de programación
funcional y por eso decimos que LISP lo es. "Programación funcional
significa, segú Graham (On Lisp, pág. 28), escribir programas que
operan a base de devolver valores en lugar de producir efectos colaterales.
Estos efectos colaterales incluyen cambios destructivos en los objetos y la
asignación de variables (con setq, por ejemplo)." Sigue explicando
Graham (pág. 31) que "una función destructiva es una que
puede alterar los argumentos que se le pasan. Sólo unos pocos operadores
LISP están pensados para producir efectos colaterales. En general, los
operadores propios del lenguaje están pensados de manera tal que se
invoquen para obtener los valores que devuelven. Nombres como sort (vl-sort
),
remove
(vl-remove
) o substitute (subst
) no deben llamarnos a engaño. Si
usted quiere efectos colaterales, utilice setq
sobre el valor devuelto. Esta
misma regla sugiere" -sigue explicando Graham- "que algunos efectos
colaterales son inevitables. Tener la programación funcional como ideal
no implica que los programas nunca debieran tener efectos colaterales.
Sólo quiere decir que no deben tener más de los necesarios."
Esta característica de la programación funcional no es arbitraria. Citando de nuevo a Graham:
Los programadores LISP no adoptaron el estilo funcional por razones meramente estéticas. Lo usan porque facilita su trabajo. En el entorno dinámico de LISP, los programas funcionales pueden ser escritos a una velocidad poco usual, y a la vez, pueden ser inusualmente confiables.
En LISP es comparativamente fácil el depurar los programas. Una gran cantidad de información se encuentra disponible en tiempo de ejecución, lo que ayuda en el rastreo de los errores. Pero aún más importante es la facilidad con la que pueden probarse los programas. No es necesario el compilar el programa para probar su funcionamiento como un todo. Podemos probar las funciones individualmente, llamándolas desde el nivel superior del evaluador.
Esta comprobación de carácter incremental es tan valiosa que el estilo de programación LISP ha evolucionado para aprovecharla. Los programas escritos en un estilo funcional pueden ser comprendidos una función a la vez, y desde el punto de vista del lector, esta es su principal ventaja. Sin embargo, el estilo funcional se adapta perfectamente a la comprobación incremental: los programas escritos en este estilo pueden ser también probados una función a la vez. Cuando una función ni examina ni altera el estado exterior, los errores se harán aparentes de inmediato. Una función así diseñada sólo puede afectar el mundo exterior a través de los valores que devuelve. En la medida que estos valores sean los esperados, podemos confiar en el código que los produjo.
Los programadores LISP experimentados de hecho diseñan sus programas de manera que puedan ser fácilmente probados:
- Tratan de aislar los efectos colaterales en unas pocas funciones, de manera que la mayor parte del programa pueda ser escrito en un estilo puramente funcional.
- Si una función debe producir efectos colaterales, tratan de que al menos posea una interfaz funcional.
- Le dan a cada función un propósito único y bien definido
Cuando acaba de escribirse una función, pueden probarla sobre una selección de casos representativos, y una vez hecho esto pasar a la próxima función.
En LISP, como en cualquier otro lenguaje, el desarrollo se lleva a cabo en ciclos de escritura y comprobación. Pero en LISP el ciclo es muy corto: funciones aisladas, e incluso partes de funciones. Y si comprobamos todo a medida que lo escribimos, sabremos dónde buscar cuando se produzca un error: en lo último que se escribió.
Graham, On Lisp, pág. 37 y 38.
Lisp mas allá de AutoCAD
Hay implementaciones de LISP para uso en el desarrollo de aplicaciones de
todo tipo. El lenguaje se ha normalizado con el nombre de Common
LISP (norma ANSI).
Existen entornos de desarrollo disponibles
muchas veces como software gratuito a través de internet. Para más información
se recomienda acceder a los siguientes sitios WEB*:
Compiladores y entornos de desarrollo Common LISP para WINDOWS:
Los tres primeros son productos comerciales, pero todos ofrecen versiones
gratuitas de evaluación perfectamente adecuadas para el aprendizaje del
lenguaje.
CLISP es totalmente gratis (GPL).
LispWorks es especialmente recomendable por su claro entorno de desarrollo (IDE), la capacidad de construir fácilmente aplicaciones con una interfaz gráfica de usuario (GUI) y la licencia de evaluación que permite el utilizar el producto por tanto tiempo como se desee, con sólo unas limitaciones de menor entidad. Le acompaña una muy completa referencia en formato HTML y PDF.
Allegro CL posee herramientas para el desarrollo de interfaces gráficas mucho más completas, pero la licencia de evaluación debe ser renovada cada mes.
Corman LISP sólo brinda la posibilidad de utilizar el IDE como evaluación durante un mes, aunque el compilador en sí es gratuito y posee una consola LISP también gratuita. Para Corman Common Lisp el profesor Reini Urban ha implementado la posibilidad de su ejecución desde el entorno AutoCAD lo que que pudiera señalar un camino de desarrollo interesante para el futuro.
Tutoriales en la Red:
El libro de David Touretzky "Common Lisp: A Gentle
Introduction to Symbolic Computation", que está disponible en formato PDF.
El libro de David Lamkins "Successful Lisp: How to
Understand and Use Common Lisp" en formato html.
La Common LISP
HyperSpec, de Kent Pitman, que no es un tutorial, sino la referencia
definitiva del lenguaje. Cortesía de la casa que comercializa LispWorks.
El
libro de Guy Steele "Common LISP, the
language. 2nd Edition", más conocido como CLTL2, aunque anterior
a la norma ANSI, aún merece ser leído.
Si se trata de comprar un libro, lo recomendable sería comenzar, ya sea con
el de Paul Graham "ANSI Common
Lisp" o con el de Stephen Slade "Object-Oriented
Common LISP".
Después, el PAIP ("Paradigms of Artificial Intelligence
Programming") de Peter Norvig es una lectura obligada.
En la Web sería necesario visitar el CLiki y la página de ALU (Association of LISP Users) para más enlaces de interés.
Un caso particular es el del Corman Common Lisp, para el que Reini Urban ha implementado la posibilidad de su ejecución desde el entorno AutoCAD y que pudiera señalar un camino de desarrollo interesante para el futuro. También existen utilidades para la transferencia de programas AutoLISP-XLISP desarrolladas por Tony Tanzillo.
PARA MUESTRA...
Como una muestra de la capacidad de síntesis y abstracción a la que podemos acceder con LISP (y una buena cuota de ingenio) reproducimos este pequeño problema planteado a manera de acertijo hace unos días por Vladimir Nesterovski en el grupo de noticias autodesk.autocad.customization, y la solución propuesta otro maestro, Tony Tanzillo:
Hola todos, os propongo algo:
crear una función que para una lista de puntos
devuelva un par de puntos con los valores mínimos
y máximos (una suerte de "caja de abarque"), que
funcione para puntos n-dimensionales y que no
emplee LAMBDA o SETQ.
Debe ser simple también. :)
Por ejemplo, para '( (1 2 2) (2 5 4) (3 1 2) )
devolvería '( (1 1 2) (3 5 4) ).
Pasadlo bien, :-)
--
Vlad
Y la respuesta de Tony Tanzillo:
(defun extents (plist)
(list (apply 'mapcar (cons 'min plist))
(apply 'mapcar (cons 'max plist))))
En dos líneas de código resuelta una función que explicada en términos de programación más convencionales, recibirá dos matrices de dimensiones variables n x m y devolverá una matriz conteniendo los máximos y mínimos para cada uno de los términos de las matrices recibidas. La función EXTENTS recibe como argumento una lista de listas y devuelve otra, sin recurrir en ningún momento a la creación de variables como almacenaje intermedio. Por otra parte el argumento original permanece inalterado, es decir que se trata de una función no destructiva.
Esto a manera de muestra de lo que trataremos de exponer de aquí en
adelante.