Common Lisp ( CL ) es un dialecto del lenguaje de programación Lisp , publicado en el documento estándar ANSI INCITS 226-1994 (S2018) [1] (anteriormente X3.226-1994 (R1999) del American National Standards Institute (ANSI) . [2] Common Lisp HyperSpec , una versión HTML con hipervínculos , se ha derivado del estándar ANSI Common Lisp. [3]
El lenguaje Common Lisp fue desarrollado como un sucesor estandarizado y mejorado de MacLisp . A principios de la década de 1980, varios grupos ya estaban trabajando en diversos sucesores de MacLisp: Lisp Machine Lisp (también conocido como ZetaLisp), Spice Lisp , NIL y S-1 Lisp . Common Lisp buscó unificar, estandarizar y extender las características de estos dialectos de MacLisp. Common Lisp no es una implementación, sino más bien una especificación de lenguaje . [4] Hay disponibles varias implementaciones del estándar Common Lisp, incluido software libre y de código abierto y productos propietarios. [5] Common Lisp es un lenguaje de programación multiparadigma de propósito general . Admite una combinación de paradigmas de programación procedimental , funcional y orientada a objetos . Como lenguaje de programación dinámico , facilita el desarrollo de software evolutivo e incremental , con compilación iterativa en programas eficientes en tiempo de ejecución. Este desarrollo incremental a menudo se realiza de forma interactiva sin interrumpir la aplicación en ejecución.
También admite la anotación y conversión de tipos opcionales, que se pueden agregar según sea necesario en las etapas posteriores de creación de perfiles y optimización, para permitir que el compilador genere un código más eficiente. Por ejemplo, fixnum
puede contener un entero sin encapsular en un rango compatible con el hardware y la implementación, lo que permite una aritmética más eficiente que con enteros grandes o tipos de precisión arbitraria. De manera similar, se le puede indicar al compilador, por módulo o por función, qué tipo de nivel de seguridad se desea, mediante declaraciones de optimización .
Common Lisp incluye CLOS , un sistema de objetos que admite métodos múltiples y combinaciones de métodos. Suele implementarse con un protocolo de metaobjetos .
Common Lisp es extensible a través de características estándar como macros Lisp (transformaciones de código) y macros de lectura (analizadores de entrada para caracteres).
Common Lisp ofrece compatibilidad parcial con versiones anteriores de Maclisp y del Lisp original de John McCarthy . Esto permite que el software Lisp más antiguo se pueda trasladar a Common Lisp. [6]
El trabajo sobre Common Lisp comenzó en 1981 tras una iniciativa del director de ARPA, Bob Engelmore, para desarrollar un dialecto Lisp estándar para la comunidad. [7] Gran parte del diseño inicial del lenguaje se realizó mediante correo electrónico. [8] [9] En 1982, Guy L. Steele Jr. presentó la primera descripción general de Common Lisp en el Simposio ACM de 1982 sobre LISP y programación funcional. [10]
La primera documentación del lenguaje se publicó en 1984 como Common Lisp the Language (conocido como CLtL1), primera edición. Una segunda edición (conocida como CLtL2), publicada en 1990, incorporó muchos cambios al lenguaje, realizados durante el proceso de estandarización ANSI Common Lisp: sintaxis LOOP extendida, el Common Lisp Object System, el Condition System para el manejo de errores, una interfaz para la impresora bonita y mucho más. Pero CLtL2 no describe el estándar ANSI Common Lisp final y, por lo tanto, no es una documentación de ANSI Common Lisp. El estándar ANSI Common Lisp final se publicó en 1994. Desde entonces, no se ha publicado ninguna actualización del estándar. Varias extensiones y mejoras a Common Lisp (por ejemplo, Unicode, Concurrency, IO basado en CLOS) han sido proporcionadas por implementaciones y bibliotecas .
Common Lisp es un dialecto de Lisp. Utiliza expresiones S para indicar tanto el código como la estructura de datos. Las llamadas a funciones, los formularios de macros y los formularios especiales se escriben como listas, con el nombre del operador primero, como en estos ejemplos:
( + 2 2 ) ; suma 2 y 2, lo que da como resultado 4. El nombre de la función es '+'. Lisp no tiene operadores como tales.
( defvar *x* ) ; Asegura que existe una variable *x*, ; sin darle un valor. Los asteriscos son parte del ; nombre, que por convención denota una variable especial (global). ; El símbolo *x* también está dotado con la propiedad de que ; las vinculaciones subsiguientes son dinámicas, en lugar de léxicas. ( setf *x* 42.1 ) ; Establece la variable *x* en el valor de punto flotante 42.1
;; Define una función que eleva al cuadrado un número: ( defun square ( x ) ( * x x ))
;; Ejecuta la función: ( cuadrado 3 ) ; Devuelve 9
;; La construcción 'let' crea un ámbito para las variables locales. Aquí ;; la variable 'a' está asociada a 6 y la variable 'b' está asociada ;; a 4. Dentro de 'let' hay un 'cuerpo', donde se devuelve el último valor calculado. ;; Aquí se devuelve el resultado de sumar a y b desde la expresión 'let'. ;; Las variables a y b tienen ámbito léxico, a menos que los símbolos hayan sido ;; marcados como variables especiales (por ejemplo, mediante un DEFVAR previo). ( let (( a 6 ) ( b 4 )) ( + a b )) ; devuelve 10
Common Lisp tiene muchos tipos de datos .
Los tipos de números incluyen números enteros , proporciones , números de punto flotante y números complejos . [11] Common Lisp utiliza números grandes para representar valores numéricos de tamaño y precisión arbitrarios. El tipo de proporción representa fracciones de manera exacta, una función que no está disponible en muchos lenguajes. Common Lisp convierte automáticamente los valores numéricos entre estos tipos según corresponda.
El tipo de carácter de Common Lisp no se limita a los caracteres ASCII . La mayoría de las implementaciones modernas permiten caracteres Unicode . [12]
El tipo de símbolo es común en los lenguajes Lisp, pero en gran medida desconocido fuera de ellos. Un símbolo es un objeto de datos único y nombrado con varias partes: nombre, valor, función, lista de propiedades y paquete. De estos, la celda de valor y la celda de función son las más importantes. Los símbolos en Lisp se utilizan a menudo de forma similar a los identificadores en otros lenguajes: para contener el valor de una variable; sin embargo, hay muchos otros usos. Normalmente, cuando se evalúa un símbolo, se devuelve su valor. Algunos símbolos se evalúan a sí mismos, por ejemplo, todos los símbolos en la palabra clave package son autoevaluables. Los valores booleanos en Common Lisp se representan mediante los símbolos autoevaluables T y NIL. Common Lisp tiene espacios de nombres para símbolos, llamados "paquetes".
Hay varias funciones disponibles para redondear valores numéricos escalares de varias maneras. La función round
redondea el argumento al entero más cercano, y los casos intermedios se redondean al entero par. Las funciones truncate
, floor
y ceiling
redondean hacia cero, hacia abajo o hacia arriba respectivamente. Todas estas funciones devuelven la parte fraccionaria descartada como un valor secundario. Por ejemplo, (floor -2.5)
da como resultado −3, 0,5; (ceiling -2.5)
da como resultado −2, −0,5; (round 2.5)
da como resultado 2, 0,5; y (round 3.5)
da como resultado 4, −0,5.
Los tipos de secuencia en Common Lisp incluyen listas, vectores, vectores de bits y cadenas. Existen muchas operaciones que pueden funcionar con cualquier tipo de secuencia.
Como en casi todos los demás dialectos de Lisp, las listas en Common Lisp se componen de conses , a veces llamadas celdas o pares de cons . Un cons es una estructura de datos con dos ranuras, llamadas car y cdr . Una lista es una cadena enlazada de conses o la lista vacía. El car de cada cons se refiere a un miembro de la lista (posiblemente otra lista). El cdr de cada cons se refiere al siguiente cons, excepto el último cons de una lista, cuyo cdr se refiere al nil
valor. Los conses también se pueden usar fácilmente para implementar árboles y otras estructuras de datos complejas; aunque generalmente se recomienda usar instancias de estructura o clase en su lugar. También es posible crear estructuras de datos circulares con conses.
Common Lisp admite matrices multidimensionales y puede redimensionar dinámicamente matrices ajustables si es necesario. Las matrices multidimensionales se pueden utilizar para matemáticas matriciales. Un vector es una matriz unidimensional. Las matrices pueden tener cualquier tipo como miembros (incluso tipos mixtos en la misma matriz) o pueden especializarse para contener un tipo específico de miembros, como en un vector de bits. Por lo general, solo se admiten unos pocos tipos. Muchas implementaciones pueden optimizar las funciones de matriz cuando la matriz utilizada está especializada en tipos. Dos tipos de matriz especializados en tipos son estándar: una cadena es un vector de caracteres, mientras que un vector de bits es un vector de bits .
Las tablas hash almacenan asociaciones entre objetos de datos. Cualquier objeto puede utilizarse como clave o valor. Las tablas hash se redimensionan automáticamente según sea necesario.
Los paquetes son colecciones de símbolos que se utilizan principalmente para separar las partes de un programa en espacios de nombres . Un paquete puede exportar algunos símbolos y marcarlos como parte de una interfaz pública. Los paquetes pueden utilizar otros paquetes.
Las estructuras , de uso similar a las estructuras de C y a los registros de Pascal , representan estructuras de datos complejas arbitrarias con cualquier cantidad y tipo de campos (llamados slots ). Las estructuras permiten la herencia única.
Las clases son similares a las estructuras, pero ofrecen características más dinámicas y herencia múltiple (consulte CLOS ). Las clases se agregaron tarde a Common Lisp y existe cierta superposición conceptual con las estructuras. Los objetos creados a partir de clases se denominan Instancias . Un caso especial son las Funciones genéricas. Las Funciones genéricas son tanto funciones como instancias.
Common Lisp admite funciones de primera clase . Por ejemplo, es posible escribir funciones que tomen otras funciones como argumentos o que también devuelvan funciones. Esto permite describir operaciones muy generales.
La biblioteca Common Lisp depende en gran medida de estas funciones de orden superior. Por ejemplo, la sort
función toma un operador relacional como argumento y la función clave como argumento de palabra clave opcional. Esto se puede utilizar no solo para ordenar cualquier tipo de datos, sino también para ordenar estructuras de datos según una clave.
;; Ordena la lista usando la función > y < como operador relacional. ( sort ( list 5 2 6 3 1 4 ) #' > ) ; Devuelve (6 5 4 3 2 1) ( sort ( list 5 2 6 3 1 4 ) #' < ) ; Devuelve (1 2 3 4 5 6)
;; Ordena la lista según el primer elemento de cada sublista. ( sort ( list ' ( 9 A ) ' ( 3 B ) ' ( 4 C )) #' < :key #' first ) ; Devuelve ((3 B) (4 C) (9 A))
El modelo de evaluación de funciones es muy simple. Cuando el evaluador encuentra una forma (f a1 a2...)
, supone que el símbolo denominado f es uno de los siguientes:
lambda
.Si f es el nombre de una función, entonces los argumentos a1, a2, ..., an se evalúan en orden de izquierda a derecha y la función se encuentra y se invoca con esos valores suministrados como parámetros.
La macrodefun
define funciones donde una definición de función proporciona el nombre de la función, los nombres de los argumentos y un cuerpo de la función:
( defun cuadrado ( x ) ( * x x ))
Las definiciones de funciones pueden incluir directivas del compilador , conocidas como declaraciones , que proporcionan pistas al compilador sobre las configuraciones de optimización o los tipos de datos de los argumentos. También pueden incluir cadenas de documentación (docstrings), que el sistema Lisp puede utilizar para proporcionar documentación interactiva:
( defun square ( x ) "Calcula el cuadrado del flotante simple x." ( declare ( single-float x ) ( optimized ( speed 3 ) ( debug 0 ) ( safety 1 ))) ( the single-float ( * x x )))
Las funciones anónimas ( literales de función ) se definen mediante lambda
expresiones, por ejemplo, (lambda (x) (* x x))
para una función que eleva al cuadrado su argumento. El estilo de programación Lisp utiliza con frecuencia funciones de orden superior para las que resulta útil proporcionar funciones anónimas como argumentos.
Las funciones locales se pueden definir con flet
y labels
.
( flet (( cuadrado ( x ) ( * x x ))) ( cuadrado 3 ))
Existen otros operadores relacionados con la definición y manipulación de funciones. Por ejemplo, una función puede compilarse con el compile
operador. (Algunos sistemas Lisp ejecutan funciones utilizando un intérprete de forma predeterminada, a menos que se les indique que las compilen; otros compilan todas las funciones).
La macro defgeneric
define funciones genéricas . Las funciones genéricas son una colección de métodos . La macro defmethod
define métodos.
Los métodos pueden especializar sus parámetros en clases estándar de CLOS , clases de sistema , clases de estructura u objetos individuales. Para muchos tipos, existen clases de sistema correspondientes .
Cuando se llama a una función genérica, el envío múltiple determinará el método efectivo a utilizar.
( defgeneric añadir ( a b ))
( defmethod add (( un número ) ( b número )) ( + a b ))
( defmethod add (( a vector ) ( b número )) ( map 'vector ( lambda ( n ) ( + n b )) a ))
( defmethod add (( a vector ) ( b vector )) ( map 'vector #' + a b ))
( defmethod add (( una cadena ) ( b cadena )) ( concatenar 'cadena a b ))
( suma 2 3 ) ; devuelve 5 ( suma #( 1 2 3 4 ) 7 ) ; devuelve #(8 9 10 11) ( suma #( 1 2 3 4 ) #( 4 3 2 1 )) ; devuelve #(5 5 5 5) ( suma "COMÚN " "LISP" ) ; devuelve "LISP COMÚN"
Las funciones genéricas también son un tipo de datos de primera clase . Las funciones y los métodos genéricos tienen muchas más características que las descritas anteriormente.
El espacio de nombres para los nombres de funciones es independiente del espacio de nombres para las variables de datos. Esta es una diferencia clave entre Common Lisp y Scheme . En Common Lisp, los operadores que definen nombres en el espacio de nombres de funciones incluyen defun
, flet
, labels
, defmethod
y defgeneric
.
Para pasar una función por su nombre como argumento a otra función, se debe utilizar el function
operador especial, comúnmente abreviado como #'
. El primer sort
ejemplo anterior se refiere a la función nombrada por el símbolo >
en el espacio de nombres de la función, con el código #'>
. Por el contrario, para llamar a una función pasada de esa manera, se utilizaría el funcall
operador en el argumento.
El modelo de evaluación de Scheme es más simple: solo hay un espacio de nombres y se evalúan todas las posiciones del formulario (en cualquier orden), no solo los argumentos. Por lo tanto, el código escrito en un dialecto a veces resulta confuso para los programadores con más experiencia en el otro. Por ejemplo, a muchos programadores de Common Lisp les gusta usar nombres de variables descriptivos, como lista o cadena , que podrían causar problemas en Scheme, ya que ocultarían localmente los nombres de las funciones.
En la comunidad Lisp, el debate sobre si un espacio de nombres independiente para las funciones es una ventaja o no es motivo de controversia. Suele hablarse de este debate como el debate Lisp-1 vs. Lisp-2 . Lisp-1 hace referencia al modelo de Scheme y Lisp-2 al modelo de Common Lisp. Estos nombres fueron acuñados en un artículo de 1988 de Richard P. Gabriel y Kent Pitman , que compara ampliamente los dos enfoques. [13]
Common Lisp admite el concepto de múltiples valores [14] , donde cualquier expresión siempre tiene un único valor primario , pero también puede tener cualquier número de valores secundarios , que pueden ser recibidos e inspeccionados por los llamadores interesados. Este concepto es distinto de devolver un valor de lista, ya que los valores secundarios son completamente opcionales y se pasan a través de un canal lateral dedicado. Esto significa que los llamadores pueden permanecer completamente inconscientes de la existencia de los valores secundarios si no los necesitan, y hace que sea conveniente utilizar el mecanismo para comunicar información que a veces es útil, pero no siempre necesaria. Por ejemplo,
TRUNCATE
función [15] redondea el número dado a un entero hacia cero. Sin embargo, también devuelve un resto como valor secundario, lo que hace que sea muy fácil determinar qué valor se truncó. También admite un parámetro divisor opcional, que se puede utilizar para realizar divisiones euclidianas de manera trivial:( let (( x 1266778 ) ( y 458 )) ( enlace-de-valores-múltiples ( cociente resto ) ( truncar x y ) ( formato nil "~A dividido por ~A es ~A resto ~A" x y cociente resto ))) ;;;; => "1266778 dividido por 458 es 2765 resto 408"
GETHASH
[16] devuelve el valor de una clave en un mapa asociativo , o el valor predeterminado en caso contrario, y un booleano secundario que indica si se encontró el valor. Por lo tanto, el código al que no le importa si el valor se encontró o se proporcionó como predeterminado puede simplemente usarlo tal como está, pero cuando dicha distinción es importante, puede inspeccionar el booleano secundario y reaccionar de manera apropiada. Ambos casos de uso son compatibles con la misma llamada y ninguno se ve innecesariamente sobrecargado o restringido por el otro. Tener esta característica a nivel de lenguaje elimina la necesidad de verificar la existencia de la clave o compararla con null como se haría en otros lenguajes.( defun get-answer ( biblioteca ) ( gethash 'respuesta biblioteca 42 )) ( defun the-answer-1 ( biblioteca ) ( formato nil "La respuesta es ~A" ( biblioteca get-answer ))) ;;;; Devuelve "La respuesta es 42" si RESPUESTA no está presente en BIBLIOTECA ( defun the-answer-2 ( biblioteca ) ( enlace-de-valores-múltiples ( respuesta seguro-p ) ( obtener-respuesta biblioteca ) ( if ( no seguro-p ) "No sé" ( formato nil "La respuesta es ~A" respuesta )))) ;;;; Devuelve "No sé" si RESPUESTA no está presente en BIBLIOTECA
Los valores múltiples son compatibles con un puñado de formularios estándar, los más comunes de los cuales son el MULTIPLE-VALUE-BIND
formulario especial para acceder a valores secundarios y VALUES
para devolver valores múltiples:
( defun magic-eight-ball () "Devuelve una predicción de perspectiva, con la probabilidad como valor secundario" ( values "Perspectiva buena" ( random 1.0 ))) ;;;; => "Perspectiva buena" ;;;; => 0.3187
Otros tipos de datos en Common Lisp incluyen:
Al igual que los programas de muchos otros lenguajes de programación, los programas Common Lisp utilizan nombres para hacer referencia a variables, funciones y muchos otros tipos de entidades. Las referencias con nombre están sujetas a un alcance.
La asociación entre un nombre y la entidad a la que el nombre hace referencia se denomina enlace.
El alcance se refiere al conjunto de circunstancias en las que se determina que un nombre tiene un vínculo particular.
Las circunstancias que determinan el alcance en Common Lisp incluyen:
(go x)
significa transferir el control a la etiqueta x
, mientras que (print x)
se refiere a la variable x
. Ambos ámbitos de x
pueden estar activos en la misma región del texto del programa, ya que las etiquetas del cuerpo de la etiqueta están en un espacio de nombres separado de los nombres de las variables. Una forma especial o forma de macro tiene control completo sobre los significados de todos los símbolos en su sintaxis. Por ejemplo, en (defclass x (a b) ())
, una definición de clase, el (a b)
es una lista de clases base, por lo que estos nombres se buscan en el espacio de nombres de clase, y x
no es una referencia a un enlace existente, sino el nombre de una nueva clase que se deriva de a
y b
. Estos hechos surgen puramente de la semántica de defclass
. El único hecho genérico sobre esta expresión es que defclass
se refiere a un enlace de macro; todo lo demás depende de defclass
.x
está incluida en una construcción de enlace como una letque define un enlace para x
, entonces la referencia está en el ámbito creado por ese enlace.Para entender a qué se refiere un símbolo, el programador Common Lisp debe saber qué tipo de referencia se está expresando, qué tipo de ámbito utiliza si es una referencia de variable (ámbito dinámico versus léxico) y también la situación de tiempo de ejecución: en qué entorno se resuelve la referencia, dónde se introdujo el enlace en el entorno, etcétera.
Algunos entornos de Lisp son de alcance global. Por ejemplo, si se define un nuevo tipo, se conocerá en todas partes a partir de entonces. Las referencias a ese tipo lo buscan en este entorno global.
Un tipo de entorno en Common Lisp es el entorno dinámico. Los enlaces establecidos en este entorno tienen una extensión dinámica, lo que significa que un enlace se establece al comienzo de la ejecución de algún constructo, como un let
bloque, y desaparece cuando ese constructo termina de ejecutarse: su duración está ligada a la activación y desactivación dinámica de un bloque. Sin embargo, un enlace dinámico no solo es visible dentro de ese bloque; también es visible para todas las funciones invocadas desde ese bloque. Este tipo de visibilidad se conoce como alcance indefinido. Se dice que los enlaces que presentan una extensión dinámica (duración ligada a la activación y desactivación de un bloque) y un alcance indefinido (visible para todas las funciones que se invocan desde ese bloque) tienen alcance dinámico.
Common Lisp admite variables con alcance dinámico, también llamadas variables especiales. Algunos otros tipos de enlaces también tienen necesariamente un alcance dinámico, como las etiquetas de reinicio y de captura. Los enlaces de funciones no pueden tener un alcance dinámico usando flet
(que solo proporciona enlaces de funciones con alcance léxico), pero los objetos de función (un objeto de primer nivel en Common Lisp) pueden asignarse a variables con alcance dinámico, vincularse usando let
en el alcance dinámico y luego llamarse usando funcall
o APPLY
.
El alcance dinámico es extremadamente útil porque agrega claridad referencial y disciplina a las variables globales . Las variables globales están mal vistas en la informática como posibles fuentes de error, porque pueden dar lugar a canales de comunicación encubiertos y ad hoc entre módulos que conducen a interacciones no deseadas y sorprendentes.
En Common Lisp, una variable especial que solo tiene un enlace de nivel superior se comporta igual que una variable global en otros lenguajes de programación. Se puede almacenar un nuevo valor en ella y ese valor simplemente reemplaza lo que está en el enlace de nivel superior. El reemplazo descuidado del valor de una variable global es la causa principal de los errores causados por el uso de variables globales. Sin embargo, otra forma de trabajar con una variable especial es darle un nuevo enlace local dentro de una expresión. Esto a veces se conoce como "reenlazar" la variable. La vinculación de una variable con ámbito dinámico crea temporalmente una nueva ubicación de memoria para esa variable y asocia el nombre con esa ubicación. Mientras ese enlace está en efecto, todas las referencias a esa variable se refieren al nuevo enlace; el enlace anterior se oculta. Cuando finaliza la ejecución de la expresión de enlace, la ubicación de memoria temporal desaparece y se revela el enlace anterior, con el valor original intacto. Por supuesto, se pueden anidar múltiples enlaces dinámicos para la misma variable.
En las implementaciones de Common Lisp que admiten subprocesos múltiples, los ámbitos dinámicos son específicos de cada subproceso de ejecución. Por lo tanto, las variables especiales sirven como una abstracción para el almacenamiento local de subprocesos. Si un subproceso vuelve a vincular una variable especial, esta revinculación no tiene efecto sobre esa variable en otros subprocesos. El valor almacenado en una vinculación solo puede ser recuperado por el subproceso que creó esa vinculación. Si cada subproceso vincula alguna variable especial *x*
, entonces *x*
se comporta como almacenamiento local de subprocesos. Entre los subprocesos que no vuelven a vincular *x*
, se comporta como un global ordinario: todos estos subprocesos hacen referencia a la misma vinculación de nivel superior de *x*
.
Las variables dinámicas se pueden utilizar para ampliar el contexto de ejecución con información de contexto adicional que se pasa implícitamente de una función a otra sin tener que aparecer como un parámetro de función adicional. Esto es especialmente útil cuando la transferencia de control tiene que pasar por capas de código no relacionado, que simplemente no se pueden ampliar con parámetros adicionales para pasar los datos adicionales. Una situación como esta suele requerir una variable global. Esa variable global se debe guardar y restaurar, de modo que el esquema no se rompa bajo la recursión: la revinculación de variables dinámicas se encarga de esto. Y esa variable debe hacerse local para el subproceso (o de lo contrario se debe utilizar un mutex grande) para que el esquema no se rompa bajo los subprocesos: las implementaciones de ámbito dinámico también pueden encargarse de esto.
En la biblioteca Common Lisp, hay muchas variables especiales estándar. Por ejemplo, todos los flujos de E/S estándar se almacenan en los enlaces de nivel superior de variables especiales conocidas. El flujo de salida estándar se almacena en *standard-output*.
Supongamos que una función foo escribe en la salida estándar:
( defun foo () ( formato t "Hola, mundo" )
Para capturar su salida en una cadena de caracteres, *standard-output* se puede vincular a un flujo de cadena y llamar:
( con-salida-a-cadena ( *salida-estándar* ) ( foo ))
-> "Hola, mundo"; la salida recopilada se devuelve como una cadena
Common Lisp admite entornos léxicos. Formalmente, los enlaces en un entorno léxico tienen un alcance léxico y pueden tener una extensión indefinida o dinámica, según el tipo de espacio de nombres. El alcance léxico significa que la visibilidad está restringida físicamente al bloque en el que se establece el enlace. Las referencias que no están textualmente (es decir, léxicamente) incrustadas en ese bloque simplemente no ven ese enlace.
Las etiquetas de un TAGBODY tienen alcance léxico. La expresión (GO X) es errónea si no está incrustada en un TAGBODY que contiene una etiqueta X. Sin embargo, los enlaces de etiquetas desaparecen cuando el TAGBODY finaliza su ejecución, porque tienen alcance dinámico. Si ese bloque de código se vuelve a ingresar mediante la invocación de un cierre léxico , no es válido que el cuerpo de ese cierre intente transferir el control a una etiqueta a través de GO:
( defvar *stashed* ) ;; contendrá una función ( tagbody ( setf *stashed* ( lambda () ( go some-label ))) ( go end-label ) ;; omitir (imprimir "Hola") some-label ( imprimir "Hola" ) end-label ) -> NIL
Cuando se ejecuta TAGBODY, primero evalúa la forma setf que almacena una función en la variable especial *stashed*. Luego (go end-label) transfiere el control a end-label, salteándose el código (print "Hello"). Dado que end-label está al final del cuerpo de la etiqueta, este termina, dando como resultado NIL. Supongamos que ahora se llama a la función recordada anteriormente:
( funcall *escondido* ) ;; ¡Error!
Esta situación es errónea. La respuesta de una implementación es una condición de error que contiene el mensaje "GO: el cuerpo de etiqueta para la etiqueta SOME-LABEL ya se ha dejado". La función intentó evaluar (go some-label), que está léxicamente incrustado en el cuerpo de etiqueta, y se resuelve en la etiqueta. Sin embargo, el cuerpo de etiqueta no se está ejecutando (su extensión ha finalizado), por lo que la transferencia de control no puede tener lugar.
Los enlaces de funciones locales en Lisp tienen un alcance léxico , y los enlaces de variables también tienen un alcance léxico por defecto. A diferencia de las etiquetas GO, ambas tienen una extensión indefinida. Cuando se establece un enlace de función o variable léxica, ese enlace continúa existiendo mientras sean posibles las referencias a él, incluso después de que haya terminado la construcción que estableció ese enlace. Las referencias a variables y funciones léxicas después de la terminación de su construcción de establecimiento son posibles gracias a los cierres léxicos .
La vinculación léxica es el modo de vinculación predeterminado para las variables de Common Lisp. Para un símbolo individual, se puede cambiar a un ámbito dinámico, ya sea mediante una declaración local o mediante una declaración global. Esto último puede ocurrir implícitamente mediante el uso de una construcción como DEFVAR o DEFPARAMETER. Es una convención importante en la programación de Common Lisp que las variables especiales (es decir, de ámbito dinámico) tengan nombres que comiencen y terminen con un asterisco, en *
lo que se denomina la " convención de las orejeras ". [17] Si se respeta, esta convención crea efectivamente un espacio de nombres separado para las variables especiales, de modo que las variables que se pretende que sean léxicas no se conviertan en especiales accidentalmente.
El alcance léxico es útil por varias razones.
En primer lugar, las referencias a variables y funciones se pueden compilar en código de máquina eficiente, porque la estructura del entorno de ejecución es relativamente simple. En muchos casos, se puede optimizar para el almacenamiento en pila, por lo que abrir y cerrar ámbitos léxicos tiene una sobrecarga mínima. Incluso en los casos en los que se deben generar cierres completos, el acceso al entorno del cierre sigue siendo eficiente; normalmente, cada variable se convierte en un desplazamiento en un vector de enlaces, y así una referencia de variable se convierte en una simple instrucción de carga o almacenamiento con un modo de direccionamiento de base más desplazamiento .
En segundo lugar, el alcance léxico (combinado con la extensión indefinida) da lugar al cierre léxico , que a su vez crea todo un paradigma de programación centrado en el uso de funciones que son objetos de primera clase, que es la raíz de la programación funcional.
En tercer lugar, y quizás lo más importante, incluso si no se explotan los cierres léxicos, el uso del ámbito léxico aísla los módulos del programa de interacciones no deseadas. Debido a su visibilidad restringida, las variables léxicas son privadas. Si un módulo A vincula una variable léxica X y llama a otro módulo B, las referencias a X en B no se resolverán accidentalmente en el límite de X en A. B simplemente no tiene acceso a X. Para situaciones en las que son deseables las interacciones disciplinadas a través de una variable, Common Lisp proporciona variables especiales. Las variables especiales permiten que un módulo A establezca un enlace para una variable X que sea visible para otro módulo B, llamado desde A. Poder hacer esto es una ventaja, y poder evitar que suceda también es una ventaja; en consecuencia, Common Lisp admite tanto el ámbito léxico como el dinámico .
En Lisp, una macro se parece superficialmente a una función. Sin embargo, en lugar de representar una expresión que se evalúa, representa una transformación del código fuente del programa. La macro obtiene el código fuente que rodea como argumentos, los vincula a sus parámetros y calcula una nueva forma de código fuente. Esta nueva forma también puede utilizar una macro. La expansión de la macro se repite hasta que la nueva forma de código fuente no utilice una macro. La forma calculada final es el código fuente que se ejecuta en tiempo de ejecución.
Usos típicos de macros en Lisp:
También es necesario implementar como macros varias funciones estándar de Common Lisp, como:
setf
abstracción estándar, para permitir expansiones personalizadas en tiempo de compilación de operadores de asignación/acceso.with-accessors
, with-slots
, y otras macros with-open-file
similaresWITH
if
o cond
es una macro construida sobre la otra, el operador especial; when
y unless
consisten en macrosloop
lenguaje específico del dominioLas macros se definen mediante la macro defmacro . El operador especial macrolet permite la definición de macros locales (con alcance léxico). También es posible definir macros para símbolos mediante define-symbol-macro y symbol-macrolet .
El libro de Paul Graham , On Lisp, describe en detalle el uso de macros en Common Lisp. El libro de Doug Hoyte, Let Over Lambda, amplía el debate sobre las macros, afirmando que "las macros son la mayor ventaja que tiene Lisp como lenguaje de programación y la mayor ventaja de cualquier lenguaje de programación". Hoyte proporciona varios ejemplos de desarrollo iterativo de macros.
Las macros permiten a los programadores de Lisp crear nuevas formas sintácticas en el lenguaje. Un uso típico es crear nuevas estructuras de control. La macro de ejemplo proporciona una until
construcción de bucle. La sintaxis es:
(hasta el formato de prueba*)
La definición de macro para hasta :
( defmacro hasta ( prueba & cuerpo cuerpo ) ( let (( etiqueta-inicio ( gensym "INICIO" )) ( etiqueta-fin ( gensym "FIN" ))) ` ( cuerpo-etiqueta , etiqueta-inicio ( cuando , prueba ( ir , etiqueta-fin )) ( progn , @ cuerpo ) ( ir , etiqueta-inicio ) , etiqueta-fin )))
tagbody es un operador especial primitivo de Common Lisp que proporciona la capacidad de nombrar etiquetas y usar la forma go para saltar a esas etiquetas. La comilla invertida ` proporciona una notación que proporciona plantillas de código, donde se completa el valor de las formas precedidas por una coma. Las formas precedidas por una coma y un signo arroba se insertan . La forma tagbody prueba la condición final. Si la condición es verdadera, salta a la etiqueta final. De lo contrario, se ejecuta el código del cuerpo proporcionado y luego salta a la etiqueta inicial.
Un ejemplo del uso de la macro hasta anterior :
( hasta ( = ( aleatorio 10 ) 0 ) ( línea de escritura "Hola" ))
El código se puede expandir utilizando la función macroexpand-1 . La expansión del ejemplo anterior se ve así:
( TAGBODY #:START1136 ( CUANDO ( ZEROP ( ALEATORIO 10 )) ( GO #:END1137 )) ( PROGN ( WRITE-LINE "hola" )) ( GO #:START1136 ) #:END1137 )
Durante la expansión de la macro, el valor de la variable test es (= (random 10) 0) y el valor de la variable body es ((write-line "Hello")) . El cuerpo es una lista de formularios.
Los símbolos se convierten automáticamente en mayúsculas y minúsculas. La expansión utiliza TAGBODY con dos etiquetas. Los símbolos para estas etiquetas son calculados por GENSYM y no se almacenan en ningún paquete. Dos formas go utilizan estas etiquetas para saltar a ellas. Dado que tagbody es un operador primitivo en Common Lisp (y no una macro), no se expandirá a otra cosa. La forma expandida utiliza la macro when , que también se expandirá. La expansión completa de una forma fuente se denomina " code walking" .
En la forma completamente expandida ( walked ), la forma when se reemplaza por la primitiva if :
( TAGBODY #:START1136 ( IF ( ZEROP ( RANDOM 10 )) ( PROGN ( GO #:END1137 )) NIL ) ( PROGN ( WRITE-LINE "hola" )) ( GO #:START1136 )) #:END1137 )
Todas las macros deben expandirse antes de que el código fuente que las contiene pueda evaluarse o compilarse normalmente. Las macros pueden considerarse funciones que aceptan y devuelven expresiones S , similares a los árboles de sintaxis abstracta , pero no limitadas a ellos. Estas funciones se invocan antes que el evaluador o compilador para producir el código fuente final. Las macros se escriben en Common Lisp normal y pueden usar cualquier operador de Common Lisp (o de terceros) disponible.
Las macros de Common Lisp son capaces de realizar lo que comúnmente se denomina captura de variables , donde los símbolos en el cuerpo de la expansión de la macro coinciden con los del contexto de llamada, lo que permite al programador crear macros en las que varios símbolos tienen un significado especial. El término captura de variables es un tanto engañoso, porque todos los espacios de nombres son vulnerables a la captura no deseada, incluidos los espacios de nombres de operadores y funciones, los espacios de nombres de etiquetas de cuerpo de etiqueta, etiquetas de captura, manejadores de condiciones y espacios de nombres de reinicio.
La captura de variables puede introducir defectos en el software. Esto sucede de una de las dos maneras siguientes:
El dialecto Scheme de Lisp proporciona un sistema de escritura de macros que proporciona la transparencia referencial que elimina ambos tipos de problemas de captura. Este tipo de sistema de macros a veces se denomina "higiénico", en particular por sus defensores (que consideran que los sistemas de macros que no resuelven automáticamente este problema son antihigiénicos). [ cita requerida ]
En Common Lisp, la higiene macro se garantiza de dos maneras diferentes.
Un enfoque es utilizar gensyms : símbolos garantizados únicos que pueden utilizarse en una macroexpansión sin amenaza de captura. El uso de gensyms en una definición de macro es una tarea manual, pero se pueden escribir macros que simplifiquen la instanciación y el uso de gensyms. Los gensyms resuelven la captura de tipo 2 fácilmente, pero no son aplicables a la captura de tipo 1 de la misma manera, porque la macroexpansión no puede cambiar el nombre de los símbolos que interfieren en el código circundante que capturan sus referencias. Los gensyms se pueden utilizar para proporcionar alias estables para los símbolos globales que necesita la macroexpansión. La macroexpansión utilizaría estos alias secretos en lugar de los nombres conocidos, por lo que la redefinición de los nombres conocidos no tendría ningún efecto negativo en la macro.
Otro enfoque es el uso de paquetes. Una macro definida en su propio paquete puede simplemente usar símbolos internos de ese paquete en su expansión. El uso de paquetes se ocupa de la captura de tipo 1 y tipo 2.
Sin embargo, los paquetes no resuelven la captura de tipo 1 de referencias a funciones y operadores estándar de Common Lisp. La razón es que el uso de paquetes para resolver problemas de captura gira en torno al uso de símbolos privados (símbolos en un paquete, que no se importan ni se hacen visibles en otros paquetes). Mientras que los símbolos de la biblioteca Common Lisp son externos y con frecuencia se importan o se hacen visibles en paquetes definidos por el usuario.
El siguiente es un ejemplo de captura no deseada en el espacio de nombres del operador, que ocurre en la expansión de una macro:
;; la expansión de UNTIL hace un uso liberal de DO ( defmacro hasta ( expresión &cuerpo cuerpo ) ` ( do ( ) ( , expresión ) ,@ cuerpo )) ;; macrolet establece la vinculación del operador léxico para DO ( macrolet (( do ( ... ) ... algo más ... )) ( hasta que ( = ( aleatorio 10 ) 0 ) ( línea de escritura "Hola" )))
La until
macro se expandirá a una forma que invoque do
lo que pretende hacer referencia a la macro estándar de Common Lisp do
. Sin embargo, en este contexto, do
puede tener un significado completamente diferente, por lo que until
es posible que no funcione correctamente.
Common Lisp resuelve el problema del sombreado de operadores y funciones estándar al prohibir su redefinición. Debido a que redefine el operador estándar do
, lo anterior es en realidad un fragmento de Common Lisp no conforme, lo que permite que las implementaciones lo diagnostiquen y lo rechacen.
El sistema de condiciones es responsable del manejo de excepciones en Common Lisp. [18] Proporciona condiciones , manejadores y reinicios . Las condiciones son objetos que describen una situación excepcional (por ejemplo, un error). Si se señala una condición , el sistema Common Lisp busca un manejador para este tipo de condición y llama al manejador. El manejador ahora puede buscar reinicios y usar uno de estos reinicios para reparar automáticamente el problema actual, usando información como el tipo de condición y cualquier información relevante proporcionada como parte del objeto de condición, y llamar a la función de reinicio apropiada.
Estos reinicios, si no son controlados por código, pueden presentarse a los usuarios (como parte de una interfaz de usuario, la de un depurador, por ejemplo), de modo que el usuario pueda seleccionar e invocar uno de los reinicios disponibles. Dado que el controlador de condiciones se llama en el contexto del error (sin desenrollar la pila), la recuperación completa del error es posible en muchos casos, donde otros sistemas de manejo de excepciones ya habrían terminado la rutina actual. El depurador en sí también puede personalizarse o reemplazarse utilizando la *debugger-hook*
variable dinámica. El código que se encuentra dentro de los formularios de protección de desenrollado, como los finalizadores, también se ejecutará según corresponda a pesar de la excepción.
En el siguiente ejemplo (utilizando Symbolics Genera ), el usuario intenta abrir un archivo en una prueba de función Lisp llamada desde Read-Eval-Print-LOOP ( REPL ), cuando el archivo no existe. El sistema Lisp presenta cuatro reinicios. El usuario selecciona Retry OPEN utilizando una ruta de reinicio diferente e ingresa una ruta de reinicio diferente (lispm-init.lisp en lugar de lispm-int.lisp). El código de usuario no contiene ningún código de manejo de errores. Todo el código de manejo de errores y reinicio lo proporciona el sistema Lisp, que puede manejar y reparar el error sin terminar el código de usuario.
Comando: (prueba ">zippy>lispm-int.lisp")Error: No se encontró el archivo. Para lispm:>zippy>lispm-int.lisp.newestLMFS:ABIERTO-LOCAL-LMFS-1 Argumento 0: #P"lispm:>zippy>lispm-int.lisp.newest"sA, <Reanudar>: Reintentar ABRIR lispm:>zippy>lispm-int.lisp.newestsB: Vuelva a intentar ABRIR utilizando una ruta diferentesC, <Abort>: Regresar al nivel superior de Lisp en un servidor TELNETsD: Reiniciar proceso terminal TELNET-> Reintentar ABRIR usando una ruta diferenteUtilice la ruta de acceso en su lugar [lispm predeterminado:>zippy>lispm-int.lisp.newest]: lispm:>zippy>lispm-init.lisp.más nuevo...el programa continúa
Common Lisp incluye un conjunto de herramientas para programación orientada a objetos , el Common Lisp Object System o CLOS . Peter Norvig explica cómo muchos patrones de diseño son más simples de implementar en un lenguaje dinámico con las características de CLOS (herencia múltiple, mixins, métodos múltiples, metaclases, combinaciones de métodos, etc.). [19] Se han propuesto varias extensiones de Common Lisp para programación orientada a objetos para incluirlas en el estándar ANSI Common Lisp, pero finalmente CLOS se adoptó como el sistema de objetos estándar para Common Lisp. CLOS es un sistema de objetos dinámico con envío múltiple y herencia múltiple , y difiere radicalmente de las facilidades OOP que se encuentran en lenguajes estáticos como C++ o Java . Como sistema de objetos dinámico, CLOS permite cambios en tiempo de ejecución en funciones y clases genéricas. Se pueden agregar y eliminar métodos, se pueden agregar y redefinir clases, se pueden actualizar objetos para cambios de clase y se puede cambiar la clase de objetos.
CLOS se ha integrado en ANSI Common Lisp. Las funciones genéricas se pueden utilizar como funciones normales y son un tipo de datos de primera clase. Cada clase CLOS está integrada en el sistema de tipos Common Lisp. Muchos tipos Common Lisp tienen una clase correspondiente. Hay más uso potencial de CLOS para Common Lisp. La especificación no dice si las condiciones se implementan con CLOS. Los nombres de ruta y los flujos se podrían implementar con CLOS. Estas posibilidades de uso adicionales de CLOS para ANSI Common Lisp no son parte del estándar. Las implementaciones reales de Common Lisp utilizan CLOS para nombres de ruta, flujos, entrada-salida, condiciones, la implementación de CLOS en sí y más.
Un intérprete de Lisp ejecuta directamente el código fuente de Lisp proporcionado como objetos de Lisp (listas, símbolos, números, etc.) leídos desde expresiones-s. Un compilador de Lisp genera código de bytes o código de máquina a partir del código fuente de Lisp. Common Lisp permite compilar funciones de Lisp individuales en memoria y compilar archivos completos en código compilado almacenado externamente ( archivos fasl ).
Varias implementaciones de dialectos Lisp anteriores proporcionaban tanto un intérprete como un compilador. Desafortunadamente, a menudo la semántica era diferente. Estos Lisp anteriores implementaban el alcance léxico en el compilador y el alcance dinámico en el intérprete. Common Lisp requiere que tanto el intérprete como el compilador utilicen el alcance léxico de forma predeterminada. El estándar Common Lisp describe tanto la semántica del intérprete como de un compilador. El compilador puede ser llamado utilizando la función compile para funciones individuales y utilizando la función compile-file para archivos. Common Lisp permite declaraciones de tipos y proporciona formas de influir en la política de generación de código del compilador. Para este último, se pueden dar valores entre 0 (no importante) y 3 (el más importante) a varias cualidades de optimización: speed , space , safety , debug y compilation-speed .
También hay una función para evaluar código Lisp: eval
. eval
toma el código como expresiones s preanalizadas y no, como en otros lenguajes, como cadenas de texto. De esta manera, el código se puede construir con las funciones Lisp habituales para construir listas y símbolos y luego este código se puede evaluar con la función eval
. Varias implementaciones de Common Lisp (como Clozure CL y SBCL) se implementan eval
utilizando su compilador. De esta manera, el código se compila, aunque se evalúe utilizando la función eval
.
El compilador de archivos se invoca mediante la función compile-file . El archivo generado con el código compilado se denomina archivo fasl (de fast load ). Estos archivos fasl y también los archivos de código fuente se pueden cargar con la función load en un sistema Common Lisp en ejecución. Según la implementación, el compilador de archivos genera código de bytes (por ejemplo, para la máquina virtual de Java ), código en lenguaje C (que luego se compila con un compilador de C) o, directamente, código nativo.
Las implementaciones de Common Lisp se pueden utilizar de forma interactiva, aunque el código se compile por completo. Por lo tanto, la idea de un lenguaje interpretado no se aplica a Common Lisp interactivo.
El lenguaje hace una distinción entre tiempo de lectura, tiempo de compilación, tiempo de carga y tiempo de ejecución, y permite que el código de usuario también haga esta distinción para realizar el tipo de procesamiento deseado en el paso deseado.
Se proporcionan algunos operadores especiales para adaptarse especialmente al desarrollo interactivo; por ejemplo, defvar
solo asignará un valor a su variable proporcionada si no estaba ya vinculada, mientras que defparameter
siempre realizará la asignación. Esta distinción es útil cuando se evalúa, compila y carga código de forma interactiva en una imagen en vivo.
También se proporcionan algunas funciones para ayudar a escribir compiladores e intérpretes. Los símbolos consisten en objetos de primer nivel y son manipulables directamente por el código del usuario. El progv
operador especial permite crear enlaces léxicos mediante programación, mientras que los paquetes también son manipulables. El compilador de Lisp está disponible en tiempo de ejecución para compilar archivos o funciones individuales. Esto facilita el uso de Lisp como compilador o intérprete intermedio para otro lenguaje.
El siguiente programa calcula el número más pequeño de personas en una sala para las cuales la probabilidad de cumpleaños únicos es menor del 50% (la paradoja del cumpleaños , donde para 1 persona la probabilidad es obviamente del 100%, para 2 es 364/365, etc.). La respuesta es 23.
En Common Lisp, por convención, las constantes se encierran entre caracteres +.
( defconstant + año-tamaño + 365 ) ( defun paradoja-de-cumpleaños ( probabilidad numero-de-personas ) ( let (( nueva-probabilidad ( * ( / ( - +año-tamaño+ numero-de-personas ) +año-tamaño+ ) probabilidad ))) ( if ( < nueva-probabilidad 0.5 ) ( 1+ numero-de-personas ) ( paradoja-de-cumpleaños nueva-probabilidad ( 1+ numero-de-personas )))))
Llamar a la función de ejemplo utilizando REPL (Read Eval Print Loop):
CL-USER > (cumpleaños-paradoja 1.0 1)23
Definimos una clase person
y un método para mostrar el nombre y la edad de una persona. A continuación, definimos un grupo de personas como una lista de person
objetos. Luego, iteramos sobre la lista ordenada.
( defclass person () (( name :initarg :name :accessor person-name ) ( age :initarg :age :accessor person-age )) ( :documentation "La clase PERSONA con los espacios NOMBRE y EDAD." )) ( defmethod display (( object person ) stream ) "Mostrando un objeto PERSONA en un flujo de salida." ( with-slots ( name age ) object ( format stream "~a (~a)" name age ))) ( defparameter *group* ( list ( make-instance 'person :name "Bob" :age 33 ) ( make-instance 'person :name "Chris" :age 16 ) ( make-instance 'person :name "Ash" :age 23 )) "Una lista de objetos PERSONA." ) ( dolist ( persona ( sort ( lista-de-copias *grupo* ) #' > :key #' edad-de-persona )) ( mostrar persona *salida-estándar* ) ( terpri ))
Imprime los tres nombres con edad descendente.
Bob (33)Ceniza (23)Cristóbal (16)
Se demuestra el uso de la macro LOOP:
( defun potencia ( x n ) ( bucle con resultado = 1 mientras ( plusp n ) cuando ( oddp n ) hacer ( setf resultado ( * resultado x )) hacer ( setf x ( * x x ) n ( truncar n 2 )) finalmente ( devolver resultado )))
Ejemplo de uso:
CL-USUARIO > ( potencia 2 200 ) 1606938044258990275541962092341162602522202993782792835301376
Compárese con la exponenciación incorporada:
CL-USUARIO > ( = ( expt 2 200 ) ( potencia 2 200 )) T
WITH-OPEN-FILE es una macro que abre un archivo y proporciona un flujo de datos. Cuando se devuelve el formulario, el archivo se cierra automáticamente. FUNCALL llama a un objeto de función. LOOP recopila todas las líneas que coinciden con el predicado.
( defun list-matching-lines ( file predicate ) "Devuelve una lista de líneas en el archivo, para las cuales el predicado aplicado a la línea devuelve T." ( with-open-file ( stream file ) ( loop for line = ( read-line stream nil nil ) while line when ( funcall predicate line ) collect it )))
La función AVAILABLE-SHELLS llama a la función LIST-MATCHING-LINES anterior con una ruta de acceso y una función anónima como predicado. El predicado devuelve la ruta de acceso de un shell o NIL (si la cadena no es el nombre de archivo de un shell).
( defun available-shells ( &optional ( file #p"/etc/shells" )) ( list-matching-lines file ( lambda ( line ) ( and ( plusp ( length line )) ( char= ( char line 0 ) #\/ ) ( pathname ( string-right-trim ' ( #\space #\tab ) line ))))))
Resultados de ejemplo (en Mac OS X 10.6):
CL-USER > ( shells disponibles ) ( #P"/bin/bash" #P"/bin/csh" #P"/bin/ksh" #P"/bin/sh" #P"/bin/tcsh" #P"/bin/zsh" )
Common Lisp se compara y contrasta con Scheme con mucha frecuencia , aunque sólo sea porque son los dos dialectos de Lisp más populares. Scheme es anterior a CL y no sólo proviene de la misma tradición de Lisp, sino también de algunos de los mismos ingenieros: Guy Steele , con quien Gerald Jay Sussman diseñó Scheme, presidió el comité de estándares para Common Lisp.
Common Lisp es un lenguaje de programación de propósito general, a diferencia de las variantes de Lisp como Emacs Lisp y AutoLISP , que son lenguajes de extensión integrados en productos específicos (GNU Emacs y AutoCAD, respectivamente). A diferencia de muchos Lisp anteriores, Common Lisp (como Scheme ) utiliza el alcance de la variable léxica de manera predeterminada tanto para el código interpretado como para el compilado.
La mayoría de los sistemas Lisp cuyos diseños contribuyeron a Common Lisp (como ZetaLisp y Franz Lisp) utilizaban variables de ámbito dinámico en sus intérpretes y variables de ámbito léxico en sus compiladores. Scheme introdujo el uso exclusivo de variables de ámbito léxico en Lisp; una inspiración de ALGOL 68. CL también admite variables de ámbito dinámico, pero deben declararse explícitamente como "especiales". No existen diferencias en el ámbito entre los intérpretes y compiladores ANSI CL.
A Common Lisp se le denomina a veces Lisp-2 y a Scheme Lisp-1 , en referencia al uso de CL de espacios de nombres separados para funciones y variables. (De hecho, CL tiene muchos espacios de nombres, como los de las etiquetas go, nombres de bloques y loop
palabras clave). Existe una controversia de larga data entre los defensores de CL y Scheme sobre las compensaciones involucradas en múltiples espacios de nombres. En Scheme, es (ampliamente) necesario evitar dar nombres de variables que entren en conflicto con las funciones; las funciones de Scheme con frecuencia tienen argumentos llamados lis
, lst
, o lyst
para no entrar en conflicto con la función del sistema list
. Sin embargo, en CL es necesario hacer referencia explícita al espacio de nombres de la función cuando se pasa una función como argumento, lo que también es una ocurrencia común, como en el sort
ejemplo anterior.
CL también difiere de Scheme en su manejo de valores booleanos. Scheme usa los valores especiales #t y #f para representar la verdad y la falsedad. CL sigue la antigua convención de Lisp de usar los símbolos T y NIL, donde NIL también representa la lista vacía. En CL, cualquier valor que no sea NIL se trata como verdadero mediante condicionales, como if
, mientras que en Scheme todos los valores que no sean #f se tratan como verdaderos. Estas convenciones permiten que algunos operadores en ambos lenguajes sirvan como predicados (respondiendo a una pregunta con valor booleano) y como retorno de un valor útil para cálculos posteriores, pero en Scheme el valor '() que es equivalente a NIL en Common Lisp se evalúa como verdadero en una expresión booleana.
Por último, los documentos de estándares de Scheme requieren optimización de llamadas de cola , algo que el estándar CL no hace. La mayoría de las implementaciones de CL ofrecen optimización de llamadas de cola, aunque a menudo solo cuando el programador usa una directiva de optimización. No obstante, el estilo de codificación CL común no favorece el uso ubicuo de la recursión que prefiere el estilo Scheme: lo que un programador de Scheme expresaría con recursión de cola, un usuario CL normalmente lo expresaría con una expresión iterativa en do
, dolist
, loop
o (más recientemente) con el iterate
paquete.
Consulte la categoría Implementaciones de Common Lisp .
Common Lisp se define mediante una especificación (como Ada y C ) en lugar de una implementación (como Perl ). Existen muchas implementaciones y el estándar detalla áreas en las que pueden diferir válidamente.
Además, las implementaciones tienden a venir con extensiones, que proporcionan funcionalidad no cubierta en el estándar:
Se han creado bibliotecas de software libre y de código abierto para soportar extensiones de Common Lisp de manera portable, y se encuentran principalmente en los repositorios de los proyectos Common-Lisp.net [20] y CLOCC (Common Lisp Open Code Collection) [21] .
Las implementaciones de Common Lisp pueden usar cualquier combinación de compilación de código nativo, compilación de código de bytes o interpretación. Common Lisp ha sido diseñado para soportar compiladores incrementales , compiladores de archivos y compiladores de bloques. Las declaraciones estándar para optimizar la compilación (como la inserción de funciones en línea o la especialización de tipos) se proponen en la especificación del lenguaje. La mayoría de las implementaciones de Common Lisp compilan el código fuente a código de máquina nativo . Algunas implementaciones pueden crear aplicaciones independientes (optimizadas). Otras compilan a código de bytes interpretado , que es menos eficiente que el código nativo, pero facilita la portabilidad del código binario. Algunos compiladores compilan el código de Common Lisp a código C. La idea errónea de que Lisp es un lenguaje puramente interpretado es más probable porque los entornos Lisp proporcionan un mensaje interactivo y ese código se compila uno por uno, de forma incremental. Con Common Lisp, la compilación incremental se usa ampliamente.
Algunas implementaciones basadas en Unix ( CLISP , SBCL ) se pueden utilizar como lenguaje de script , es decir, el sistema las invoca de forma transparente, de la misma forma que un intérprete de shell de Perl o Unix . [22]
Common Lisp se utiliza para desarrollar aplicaciones de investigación (a menudo en Inteligencia Artificial ), para el desarrollo rápido de prototipos o para aplicaciones implementadas.
Common Lisp se utiliza en muchas aplicaciones comerciales, incluido el sitio de comercio web Yahoo! Store, que originalmente involucró a Paul Graham y luego fue reescrito en C++ y Perl . [47] Otros ejemplos notables incluyen:
También existen aplicaciones de código abierto escritas en Common Lisp, como:
{{cite journal}}
: Requiere citar revista |journal=
( ayuda ){{cite journal}}
: Requiere citar revista |journal=
( ayuda )Una lista cronológica de libros publicados (o a punto de publicarse) sobre Common Lisp (el lenguaje) o sobre programación con Common Lisp (especialmente programación de IA).