Ceceo común
Common Lisp (CL) es un dialecto del lenguaje de programación Lisp, publicado en el documento estándar ANSI ANSI INCITS 226-1994 (S20018) (anteriormente X3.226-1994 (R1999)). Common Lisp HyperSpec, una versión HTML con hipervínculos, se ha derivado del estándar ANSI Common Lisp.
El lenguaje Common Lisp se desarrolló 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 una especificación del lenguaje. Varias implementaciones del estándar Common Lisp están disponibles, incluido software gratuito y de código abierto y productos patentados. Common Lisp es un lenguaje de programación multiparadigma de propósito general. Admite una combinación de paradigmas de programación orientados a objetos, funcionales y procedimentales. 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 es compatible con la anotación y la 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 número entero sin recuadro en un rango admitido por el hardware y la implementación, lo que permite una aritmética más eficiente que en números enteros grandes o tipos de precisión arbitraria. De manera similar, se le puede decir al compilador por módulo o por función qué tipo de nivel de seguridad se desea, usando declaraciones optimize.
Common Lisp incluye CLOS, un sistema de objetos que admite métodos múltiples y combinaciones de métodos. A menudo se implementa con un protocolo de metaobjetos.
Common Lisp es extensible a través de funciones estándar como macros Lisp (transformaciones de código) y macros de lectura (analizadores de entrada para caracteres).
Common Lisp proporciona compatibilidad retroactiva parcial con Maclisp y el Lisp original de John McCarthy. Esto permite que el software Lisp más antiguo se transfiera a Common Lisp.
Historia
El trabajo en Common Lisp comenzó en 1981 después de una iniciativa del gerente de ARPA, Bob Engelmore, para desarrollar un único dialecto Lisp estándar de la comunidad. Gran parte del diseño del lenguaje inicial se realizó por correo electrónico. En 1982, Guy L. Steele Jr. dio la primera descripción general de Common Lisp en el Simposio ACM de 1982 sobre LISP y programación funcional.
La primera documentación del idioma 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 en el lenguaje, realizados durante el proceso de estandarización ANSI Common Lisp: sintaxis LOOP extendida, el sistema de objetos Common Lisp, el sistema de condiciones para el manejo de errores, una interfaz para el bonita impresora 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 implementaciones y bibliotecas han proporcionado varias extensiones y mejoras a Common Lisp (por ejemplo, Unicode, Concurrency, CLOS-based IO).
Sintaxis
Common Lisp es un dialecto de Lisp. Utiliza expresiones S para denotar tanto el código como la estructura de datos. Las llamadas a funciones, los formularios de macro y los formularios especiales se escriben como listas, con el nombre del operador primero, como en estos ejemplos:
()+ 2 2) ; añade 2 y 2, dando 4. El nombre de la función es '+'. Lisp no tiene operadores como tales.
()defvar *x*) ; Garantiza que exista una variable *x*, ; sin darle un valor. Los asteriscos son parte de ; el nombre, por convención denotando una variable especial (global). ; El símbolo *x* también está dotado con la propiedad que ; las uniones posteriores de ella son dinámicas, en lugar de lexicales. ()setf *x* 42.1) ; Establece la variable *x* al valor de punto flotante 42.1
; Definir una función que cuadra un número: ()defun cuadrado ()x) ()* x x)
; Ejecutar la función: ()cuadrado 3) ; Devoluciones 9
; La construcción 'demos' crea un alcance para las variables locales. Aquí. ;; la variable 'a' está atada a 6 y la variable 'b' está atada ; a 4. Dentro del 'vamos' es un 'cuerpo', donde el último valor calculado es devuelto. ; Aquí el resultado de añadir a y b se devuelve de la expresión 'demos'. ; Las variables a y b tienen alcance léxico, a menos que los símbolos hayan sido ;; marcado como variables especiales (por ejemplo por un DEFVAR anterior). ()Deja ()a 6) ()b 4) ()+ a b) ; retornos 10
Tipos de datos
Common Lisp tiene muchos tipos de datos.
Tipos escalares
Los tipos deNúmero incluyen números enteros, proporciones, números de punto flotante y números complejos. Common Lisp usa bignums para representar valores numéricos de tamaño y precisión arbitrarios. El tipo de proporción representa fracciones exactamente, una función que no está disponible en muchos idiomas. Common Lisp coacciona automáticamente los valores numéricos entre estos tipos según corresponda.
El tipo de carácter de Common Lisp no está limitado a caracteres ASCII. La mayoría de las implementaciones modernas permiten caracteres Unicode.
El tipo símbolo es común a los lenguajes Lisp, pero en gran medida desconocido fuera de ellos. Un símbolo es un objeto de datos con nombre único 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 a menudo se usan de manera similar a los identificadores en otros idiomas: 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 el paquete de palabras clave se evalúan a sí mismos. Los valores booleanos en Common Lisp están representados por los símbolos de autoevaluación 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 formas. La función round
redondea el argumento al entero más cercano, con la mitad de los casos redondeados al entero par. Las funciones truncar
, floor
y ceiling
redondean hacia cero, hacia abajo o hacia arriba respectivamente. Todas estas funciones devuelven la parte fraccionaria descartada como valor secundario. Por ejemplo, (floor -2.5)
produce −3, 0.5; (techo -2.5)
produce −2, −0.5; (alrededor de 2,5)
produce 2, 0,5; y (round 3.5)
produce 4, −0.5.
Estructuras de datos
Los tipos desecuencia en Common Lisp incluyen listas, vectores, vectores de bits y cadenas. Hay muchas operaciones que pueden funcionar en 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 de contras o pares . Una contra es una estructura de datos con dos espacios, llamados car y cdr. Una lista es una cadena enlazada de consensos o la lista vacía. Cada automóvil de los contras se refiere a un miembro de la lista (posiblemente otra lista). Cada cons's cdr se refiere a los siguientes contras, excepto por los últimos contras de una lista, cuyo cdr se refiere al valor nil
. Conses también se puede 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 cambiar dinámicamente el tamaño de matrices ajustables si es necesario. Las matrices multidimensionales se pueden utilizar para las matemáticas de matrices. Un vector es una matriz unidimensional. Los arreglos pueden tener cualquier tipo como miembros (incluso tipos mixtos en el mismo arreglo) o pueden especializarse para contener un tipo específico de miembros, como en un vector de bits. Por lo general, solo se admiten algunos tipos. Muchas implementaciones pueden optimizar las funciones de matriz cuando la matriz utilizada está especializada en tipos. Dos tipos de arreglos especializados en tipos son estándar: una cadena es un vector de caracteres, mientras que un vector de bits es un vector de bits.
Lastablas hash almacenan asociaciones entre objetos de datos. Cualquier objeto puede ser utilizado como clave o valor. Las tablas hash se redimensionan automáticamente según sea necesario.
Paquetes son colecciones de símbolos, usados principalmente para separar las partes de un programa en espacios de nombres. Un paquete puede exportar algunos símbolos, marcándolos como parte de una interfaz pública. Los paquetes pueden usar otros paquetes.
Estructuras, similares en uso a estructuras C y registros Pascal, representan estructuras de datos complejas arbitrarias con cualquier número y tipo de campos (llamados ranuras). Las estructuras permiten la herencia única.
Las clases son similares a las estructuras, pero ofrecen funciones más dinámicas y herencia múltiple. (Ver 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.
Funciones
Common Lisp admite funciones de primera clase. Por ejemplo, es posible escribir funciones que toman otras funciones como argumentos o también devuelven funciones. Esto hace posible describir operaciones muy generales.
La biblioteca Common Lisp se basa en gran medida en estas funciones de orden superior. Por ejemplo, la función sort
toma un operador relacional como argumento y la función clave como argumento de palabra clave opcional. Esto se puede usar no solo para ordenar cualquier tipo de datos, sino también para ordenar estructuras de datos de acuerdo con una clave.
; Clasifica la lista usando la función не y нерент como el operador relacional. ()especie ()lista 5 2 6 3 1 4) # '■) ; Regresos (6 5 4 3 2 1) ()especie ()lista 5 2 6 3 1 4) # '.) ; Regresos (1 2 3 4 5 6)
; Clasifica la lista según el primer elemento de cada sub-lista. ()especie ()lista '()9 A) '()3 B) '()4 C) # '. :key # 'primero) ; Devoluciones ((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...)
entonces asume que el símbolo llamado f es uno de los siguientes:
- Un operador especial (consultado con frecuencia contra una lista fija)
- Un operador macro (debe definirse previamente)
- El nombre de una función (por defecto), que puede ser un símbolo, o una subforma que comienza con el símbolo
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 e invoca con esos valores proporcionados como parámetros.
Definiendo funciones
La macro defun define funciones donde una definición de función proporciona el nombre de la función, los nombres de los argumentos y el cuerpo de la función:
()defun cuadrado ()x) ()* x x)
Las definiciones de funciones pueden incluir directivas del compilador, conocidas como declaraciones, que brindan sugerencias al compilador sobre la configuración 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 usar para proporcionar documentación interactiva:
()defun cuadrado ()x) "Calcula la plaza del único piso x." ()Declara ()monofloat x) ()optimización ()velocidad 3) ()debug 0) ()seguridad 1)) ()el monofloat ()* x x))
Las funciones anónimas (funciones literales) se definen mediante expresiones lambda
, p. (lambda (x) (* x x))
para una función que eleva al cuadrado su argumento. El estilo de programación Lisp frecuentemente usa funciones de orden superior para las cuales es útil proporcionar funciones anónimas como argumentos.
Las funciones locales se pueden definir con flet
y labels
.
()pulg ()cuadrado ()x) ()* x x)) ()cuadrado 3)
Hay varios otros operadores relacionados con la definición y manipulación de funciones. Por ejemplo, una función se puede compilar con el operador compile
. (Algunos sistemas Lisp ejecutan funciones usando un intérprete por defecto a menos que se les indique compilar; otros compilan todas las funciones).
Definir funciones y métodos genéricos
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 sobre clases estándar de CLOS, clases de sistema, clases de estructura u objetos individuales. Para muchos tipos, hay clases de sistema correspondientes.
Cuando se llama a una función genérica, el envío múltiple determinará el método efectivo a usar.
()defgeneric añadir ()a b)
()defmethod añadir ()a Número) ()b Número) ()+ a b)
()defmethod añadir ()a vector) ()b Número) ()mapa 'vector' ()lambda ()n) ()+ n b) a)
()defmethod añadir ()a vector) ()b vector) ()mapa 'vector' # '+ a b)
()defmethod añadir ()a cuerda) ()b cuerda) ()concatenate 'estring' a b)
()añadir 2 3) ; retornos 5 ()añadir #1 2 3 4) 7) ; retornos #(8 9 10 11) ()añadir #1 2 3 4) #4 3 2 1) ; retornos #(5 5 5 5) ()añadir "Common" "LISP") ; devuelve "COMMON LISP"
Las funciones genéricas también son un tipo de datos de primera clase. Hay muchas más funciones para funciones y métodos genéricos que las descritas anteriormente.
El espacio de nombres de la función
El espacio de nombres para nombres de funciones es independiente del espacio de nombres para variables de datos. Esta es una diferencia clave entre Common Lisp y Scheme. Para Common Lisp, los operadores que definen nombres en el espacio de nombres de la función incluyen defun
, flet
, labels
, defmethod
y defgenérico.
Para pasar una función por nombre como argumento a otra función, se debe usar el operador especial function
, comúnmente abreviado como #'
. El primer ejemplo de sort
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 usaría el operador funcall
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 en el formulario (en cualquier orden), no solo los argumentos. Por lo tanto, el código escrito en un dialecto a veces es 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 list o string que podrían causar problemas en Scheme, ya que ocultarían localmente los nombres de las funciones.
Si un espacio de nombres separado para las funciones es una ventaja, es una fuente de controversia en la comunidad Lisp. Por lo general, se lo conoce como el debate Lisp-1 vs. Lisp-2. Lisp-1 se refiere al modelo de Scheme y Lisp-2 se refiere al modelo de Common Lisp. Estos nombres fueron acuñados en un artículo de 1988 por Richard P. Gabriel y Kent Pitman, que compara ampliamente los dos enfoques.
Múltiples valores devueltos
Common Lisp admite el concepto de valores múltiples, donde cualquier expresión siempre tiene un solo valor principal, pero también puede tener cualquier cantidad de valores secundarios, que puede ser recibido e inspeccionado por personas interesadas en llamar. Este concepto es distinto de devolver un valor de lista, ya que los valores secundarios son completamente opcionales y pasan a través de un canal lateral dedicado. Esto significa que las personas que llaman pueden permanecer completamente inconscientes de los valores secundarios que existen 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,
- El
TRUNCATE
función redondea el número dado a un entero hacia cero. Sin embargo, también devuelve un resto como valor secundario, por lo que es muy fácil determinar qué valor fue truncado. También es compatible con un parámetro de divisor opcional, que se puede utilizar para realizar la división Euclideana trivialmente:
()Deja ()x 1266778) ()Sí. 458) ()multivalor-bind ()quotient resto) ()truncate x Sí.) ()formato Nil "~A split by ~A is ~A remainder ~A" x Sí. quotient resto));;; = título "1266778 dividido por 458 es 2765 restante 408"
GETHASH
devuelve el valor de una clave en un mapa asociativo, o el valor predeterminado de otro modo, y un booleano secundario indicando si se encontró el valor. Por lo tanto, el código que no importa si el valor fue encontrado o proporcionado como el predeterminado puede simplemente utilizarlo como es, pero cuando dicha distinción es importante, podría inspeccionar el booleano secundario y reaccionar adecuadamente. Ambos casos de uso son apoyados por la misma llamada y tampoco es innecesariamente cargado o restringido por el otro. Tener esta característica a nivel de idiomas elimina la necesidad de comprobar la existencia de la clave o compararla con nula como se haría en otros idiomas.
()defun Resistente. ()biblioteca) ()# "Alcantarillado" biblioteca 42)()defun la respuesta-1 ()biblioteca) ()formato Nil "La respuesta es ~A" ()Resistente. biblioteca));; "La respuesta es 42" si ANSWER no está presente en LIBRERO()defun la respuesta-2 ()biblioteca) ()multivalor-bind ()respuesta seguro-p) ()Resistente. biblioteca) ()si ()no seguro-p) "No lo sé" ()formato Nil "La respuesta es ~A" respuesta))); Devuelve "No lo sé" si ANSWER no está presente en LIBRARY
Los valores múltiples son compatibles con un puñado de formularios estándar, los más comunes son el formulario especial MULTIPLE-VALUE-BIND
para acceder a valores secundarios y VALUES
para devolver múltiples valores:
()defun magic-eight-ball () "Retorna una predicción de perspectivas, con la probabilidad como valor secundario" ()valores "Se ve bien" ()al azar 1.0));; "Se ve bien";;
Otros tipos
Otros tipos de datos en Common Lisp incluyen:
- Nombres de ruta Representa archivos y directorios en el sistema de archivos. La instalación Common Lisp pathname es más general que la mayoría de las convenciones de nombres de archivos de sistemas operativos, haciendo que los programas de Lisp tengan acceso a archivos ampliamente portátiles en diversos sistemas.
- Entrada y salida corrientes representan fuentes y sumideros de datos binarios o textuales, como el terminal o archivos abiertos.
- Common Lisp tiene un generador de números pseudo-aleatorios integrado (PRNG). Estado aleatorio los objetos representan fuentes reutilizables de números pseudo-aleatorios, permitiendo al usuario sembrar el PRNG o hacer que vuelva a reproducir una secuencia.
- Condiciones son un tipo utilizado para representar errores, excepciones y otros eventos "interesantes" a los que un programa puede responder.
- Clases son objetos de primera clase, y son en sí mismos instancias de clases llamadas clases de metaobjeto (metaclasses para abreviar).
- Readtables son un tipo de objeto que controla cómo el lector de Common Lisp analiza el texto del código fuente. Al controlar qué legible se utiliza cuando se lee el código, el programador puede cambiar o ampliar la sintaxis del idioma.
Alcance
Al igual que los programas en muchos otros lenguajes de programación, los programas de Common Lisp utilizan nombres para referirse a variables, funciones y muchos otros tipos de entidades. Las referencias nombradas están sujetas al alcance.
La asociación entre un nombre y la entidad a la que se refiere el nombre se denomina enlace.
Alcance se refiere al conjunto de circunstancias en las que se determina que un nombre tiene una vinculación particular.
Determinantes del alcance
Las circunstancias que determinan el alcance en Common Lisp incluyen:
- la ubicación de una referencia dentro de una expresión. Si es la posición más izquierda de un compuesto, se refiere a un operador especial o a una macro o una unión de función, de lo contrario a una unión variable o algo más.
- el tipo de expresión en el que se realiza la referencia. Por ejemplo,
(go x)
significa control de transferencia a etiquetax
, mientras(print x)
se refiere a la variablex
. Ambos ámbitosx
puede ser activo en la misma región del texto del programa, ya que las etiquetas del cuerpo de etiquetas están en un espacio de nombre separado de nombres variables. Una forma especial o forma 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, la(a b)
es una lista de clases base, por lo que estos nombres son buscados en el espacio de nombres de clase, yx
no es una referencia a una unión existente, sino el nombre de una nueva clase que se deriva dea
yb
. Estos hechos emergen puramente de la semánticadefclass
. El único hecho genérico de esta expresión es quedefclass
se refiere a una unión macro; todo lo demás está hastadefclass
. - la ubicación de la referencia dentro del texto del programa. Por ejemplo, si una referencia a la variable
x
se adjunta en un constructo vinculante como un permiso que define un vinculantex
, entonces la referencia está en el alcance creado por esa unión. - para una referencia variable, ya sea o no un símbolo variable ha sido, local o globalmente, declarado especial. Esto determina si la referencia se resuelve dentro de un entorno lexical, o dentro de un entorno dinámico.
- el caso específico del ambiente en el que se resuelve la referencia. Un ambiente es un diccionario de tiempo de ejecución que mapea símbolos a los vinculantes. Cada tipo de referencia utiliza su propio tipo de ambiente. Las referencias a variables lexicales se resuelven en un entorno lexical, etc. Más de un ambiente se puede asociar con la misma referencia. Por ejemplo, gracias a la recursión o al uso de múltiples hilos, pueden existir múltiples activaciones de la misma función al mismo tiempo. Estas activaciones comparten el mismo texto del programa, pero cada una tiene su propia instancia de entorno lexical.
Para entender a qué se refiere un símbolo, el programador de Common Lisp debe saber qué tipo de referencia se está expresando, qué tipo de alcance usa si es una referencia variable (alcance dinámico versus léxico), y también el tiempo de ejecución situación: en qué entorno se resuelve la referencia, dónde se introdujo la vinculación en el entorno, etcétera.
Tipos de entorno
Global
Algunos entornos en Lisp son globales. Por ejemplo, si se define un nuevo tipo, se conoce en todas partes a partir de entonces. Las referencias a ese tipo lo buscan en este entorno global.
Dinámico
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 se establece un enlace al comienzo de la ejecución de alguna construcción, como un bloque let
, y desaparece cuando esa construcción termina de ejecutarse: su tiempo de vida es ligado 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 muestran una extensión dinámica (tiempo de vida ligado a la activación y desactivación de un bloque) y un alcance indefinido (visible para todas las funciones que se llaman desde ese bloque) tienen un alcance dinámico.
Common Lisp tiene soporte para variables de ámbito dinámico, que también se denominan variables especiales. Ciertos otros tipos de enlaces también tienen necesariamente un alcance dinámico, como reinicios y etiquetas de captura. Los enlaces de función no pueden tener un alcance dinámico usando flet
(que solo proporciona enlaces de función con alcance léxico), pero los objetos de función (un objeto de primer nivel en Common Lisp) pueden asignarse a variables de alcance dinámico, enlazados usando let
en el alcance dinámico, luego invoque 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 informática como posibles fuentes de error, ya que pueden dar lugar a canales de comunicación encubiertos ad-hoc entre módulos que conducen a interacciones sorprendentes y no deseadas.
En Common Lisp, una variable especial que solo tiene un enlace de nivel superior se comporta como una variable global en otros lenguajes de programación. Se puede almacenar un nuevo valor en él, y ese valor simplemente reemplaza lo que está en el enlace de nivel superior. El reemplazo descuidado del valor de una variable global está en el centro de los errores causados por el uso de variables globales. Sin embargo, otra forma de trabajar con una variable especial es darle un enlace local nuevo dentro de una expresión. Esto a veces se denomina "reenlace" La variable. Vincular una variable de á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 vigor, todas las referencias a esa variable se refieren al nuevo enlace; el enlace anterior está oculto. Cuando finaliza la ejecución de la expresión de vinculación, la ubicación de la memoria temporal desaparece y se revela la vinculación 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 para cada subproceso de ejecución. Por lo tanto, las variables especiales sirven como abstracción para el almacenamiento local de subprocesos. Si un subproceso vuelve a enlazar una variable especial, esta nueva vinculación no tiene efecto sobre esa variable en otros subprocesos. El valor almacenado en un enlace solo puede ser recuperado por el subproceso que creó ese enlace. Si cada subproceso vincula alguna variable especial *x*
, entonces *x*
se comporta como almacenamiento local de subprocesos. Entre los subprocesos que no vuelven a enlazar *x*
, se comporta como un global ordinario: todos estos subprocesos se refieren al mismo enlace de nivel superior de *x*
.
Las variables dinámicas se pueden usar para extender 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 a través de 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 generalmente requiere una variable global. Esa variable global debe guardarse y restaurarse, para que el esquema no se rompa bajo la recursividad: el reenlace dinámico de variables se encarga de esto. Y esa variable debe convertirse en subproceso local (o de lo contrario, se debe usar un mutex grande) para que el esquema no se rompa debajo de los subprocesos: las implementaciones de alcance 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 *salida estándar*.
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, *salida-estándar* puede vincularse a un flujo de cadena y llamarse:
()con salida a la cadena ()*standard-output*) ()Foo)
- "Hola, mundo"
Léxico
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. Ámbito léxico significa que la visibilidad está restringida físicamente al bloque en el que se establece la vinculación. Las referencias que no están incrustadas textualmente (es decir, léxicamente) en ese bloque simplemente no ven ese enlace.
Las etiquetas en 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 termina su ejecución, porque tienen extensión dinámica. 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 *Establecido*) ; ()tagbody ()setf *Establecido* ()lambda () ()Vamos. algunos-label)) ()Vamos. end-label) ;; saltar el (print "Hola") algunos-label ()impresión "Hola") end-label) - NIL
Cuando se ejecuta TAGBODY, primero evalúa el formulario setf que almacena una función en la variable especial *escondida*. Luego (go end-label) transfiere el control a end-label, omitiendo el código (imprime "Hola"). Dado que end-label está al final del cuerpo de la etiqueta, el cuerpo de la etiqueta termina y produce NIL. Supongamos que la función recordada anteriormente ahora se llama:
()funcall *Establecido*) ;; 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 la etiqueta para la etiqueta SOME-LABEL ya se ha dejado". La función trató de evaluar (go some-label), que está incrustado léxicamente en el cuerpo de la etiqueta y se resuelve en la etiqueta. Sin embargo, el cuerpo de la etiqueta no se está ejecutando (su extensión ha finalizado), por lo que no se puede realizar la transferencia de control.
Los enlaces de funciones locales en Lisp tienen alcance léxico, y los enlaces de variables también tienen alcance léxico por defecto. A diferencia de las etiquetas GO, ambas tienen una extensión indefinida. Cuando se establece una función léxica o vinculación de variable, esa vinculación continúa existiendo mientras sea posible hacer referencia a ella, incluso después de que la construcción que estableció esa vinculación haya terminado. 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.
El enlace léxico es el modo de enlace predeterminado para las variables de Common Lisp. Para un símbolo individual, se puede cambiar al alcance 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 alcance dinámico) tengan nombres que comiencen y terminen con un asterisco sigilo *
en lo que se llama la "convención de orejeras". Si se cumple, esta convención crea efectivamente un espacio de nombres separado para variables especiales, de modo que las variables destinadas a ser 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 un código de máquina eficiente, porque la estructura del entorno de tiempo de ejecución es relativamente simple. En muchos casos, se puede optimizar para apilar almacenamiento, 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; por lo general, cada variable se convierte en un desplazamiento en un vector de enlaces, por lo que 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 a la clausura léxica, que a su vez crea todo un paradigma de programación centrado en el uso de funciones como objetos de primera clase, que es la raíz de la programación funcional.
En tercer lugar, quizás lo más importante, incluso si no se explotan los cierres léxicos, el uso del alcance léxico aísla los módulos de 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 X vinculado en A. B simplemente no tiene acceso a X. Para situaciones en las que las interacciones disciplinadas a través de una variable son deseable, Common Lisp proporciona variables especiales. Las variables especiales permiten que un módulo A configure un enlace para una variable X que es 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 alcance léxico como el dinámico.
Macros
Una macro en Lisp se parece superficialmente a una función en uso. 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 la fuente que rodea como argumentos, los vincula a sus parámetros y calcula una nueva forma de fuente. Este nuevo formulario también puede usar una macro. La expansión de la macro se repite hasta que el nuevo formulario de origen no utilice una macro. La forma computada final es el código fuente ejecutado en tiempo de ejecución.
Usos típicos de las macros en Lisp:
- nuevas estructuras de control (ejemplo: construcciones de bucle, construcciones de ramificación)
- escopia y construcciones vinculantes
- sintaxis simplificada para código fuente complejo y repetido
- formas de definición de primer nivel con efectos secundarios compilados
- programación basada en datos
- idiomas específicos de dominio integrado (ejemplos: SQL, HTML, Prolog)
- formas de finalización implícita
También es necesario implementar varias funciones estándar de Common Lisp como macros, como:
- el estándar
setf
abstracción, para permitir expansiones personalizadas de operadores de asignación/acceso with-accessors
,with-slots
,with-open-file
y otros similaresWITH
macros- Dependiendo de la aplicación,
if
ocond
es un macro construido sobre el otro, el operador especial;when
yunless
consiste en macros - El poderoso
loop
idioma específico
Las macros están definidas por la macro defmacro. El operador especial macrolet permite la definición de macros locales (de ámbito léxico). También es posible definir macros para símbolos usando 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 Let Over Lambda de Doug Hoyte amplía el debate sobre las macros y afirma que "las macros son la mayor ventaja individual que tiene lisp como lenguaje de programación y la mayor ventaja individual de cualquier lenguaje de programación". Hoyte proporciona varios ejemplos de desarrollo iterativo de macros.
Ejemplo usando una macro para definir una nueva estructura de control
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 construcción de bucle until
. La sintaxis es:
(hasta el formulario de prueba*)
La definición de macro para hasta:
()defmacro hasta ()prueba cuerpo cuerpo) ()Deja ()start-tag ()gensym "START") ()end-tag ()gensym "END")) `()tagbody ,start-tag ()cuando ,prueba ()Vamos. ,end-tag) ()progn ,@cuerpo) ()Vamos. ,start-tag) ,end-tag))
tagbody es un operador especial primitivo de Common Lisp que brinda la capacidad de nombrar etiquetas y usar el formulario ir para saltar a esas etiquetas. La comilla inversa ` proporciona una notación que proporciona plantillas de código, donde se completa el valor de los formularios precedidos por una coma. Los formularios precedidos por una coma y el signo de arroba están empalmados. El formulario de cuerpo de etiqueta prueba la condición final. Si la condición es verdadera, salta a la etiqueta final. De lo contrario, el código del cuerpo proporcionado se ejecuta y luego salta a la etiqueta de inicio.
Un ejemplo del uso de la macro hasta anterior:
()hasta ()= ()al azar 10) 0) ()Write-line "Hola")
El código se puede expandir usando la función macroexpand-1. La expansión para el ejemplo anterior se ve así:
()TAGBODY #:START1136 ()CUANDO ()ZEROP ()RANDOM 10) ()Vamos. #:END1137) ()PROGN ()WRITE-LINE "hola") ()Vamos. #: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 ((línea de escritura "Hola")). El cuerpo es una lista de formularios.
Por lo general, los símbolos se convierten automáticamente en mayúsculas. La expansión usa TAGBODY con dos etiquetas. Los símbolos para estas etiquetas son computados por GENSYM y no están incluidos en ningún paquete. Dos formularios go usan estas etiquetas para saltar. Dado que tagbody es un operador primitivo en Common Lisp (y no una macro), no se expandirá a otra cosa. El formulario expandido usa la macro when, que también se expandirá. La expansión completa de un formulario fuente se denomina recorrido de código.
En la forma completamente expandida (caminado), la forma cuando se reemplaza por la primitiva si:
()TAGBODY #:START1136 ()IF ()ZEROP ()RANDOM 10) ()PROGN ()Vamos. #:END1137) NIL) ()PROGN ()WRITE-LINE "hola") ()Vamos. #: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 sin limitarse a ellos. Estas funciones se invocan antes que el evaluador o compilador para producir el código fuente final. Las macros están escritas en Common Lisp normal y pueden usar cualquier operador Common Lisp (o de terceros) disponible.
Captura y sombreado de variables
Las macros de Common Lisp son capaces de lo que comúnmente se llama captura de variables, donde los símbolos en el cuerpo de 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 características especiales. sentido. El término captura de variable es algo engañoso, porque todos los espacios de nombres son vulnerables a la captura no deseada, incluidos los espacios de nombres de operador y función, el espacio de nombres de etiqueta de cuerpo de etiqueta, la etiqueta catch, el controlador de condiciones y los espacios de nombres de reinicio.
La captura de variables puede introducir defectos de software. Esto sucede de una de las siguientes dos maneras:
- En primer lugar, una expansión macro puede hacer inadvertidamente una referencia simbólica que el macro escritor asumió resolverá en un espacio de nombres global, pero el código donde se expande la macro resulta proporcionar una definición local de sombra que roba esa referencia. Que esto sea referido como la captura tipo 1.
- La segunda forma, tipo 2 captura, es justo lo contrario: algunos de los argumentos de la macro son piezas de código suministrado por el lector macro, y esas piezas de código están escritas de tal manera que hacen referencias a los enlaces circundantes. Sin embargo, la macro inserta estas piezas de código en una expansión que define sus propias uniones que accidentalmente captura algunas de estas referencias.
El dialecto Scheme de Lisp proporciona un sistema de macroescritura que proporciona la transparencia referencial que elimina ambos tipos de problemas de captura. Este tipo de sistema macro a veces se llama "higiénico", en particular por sus defensores (que consideran antihigiénicos los sistemas macro que no resuelven automáticamente este problema).
En Common Lisp, la higiene macro se garantiza de dos maneras diferentes.
Un enfoque es usar gensyms: símbolos únicos garantizados que se pueden usar en una macro-expansió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 creación de instancias y el uso de gensyms. Gensyms resuelve fácilmente la captura de tipo 2, pero no son aplicables a la captura de tipo 1 de la misma manera, porque la expansión macro no puede cambiar el nombre de los símbolos que interfieren en el código circundante que captura sus referencias. Gensyms podría usarse para proporcionar alias estables para los símbolos globales que necesita la expansión macro. La expansión de la macro usarí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 adverso en la macro.
Otro enfoque es usar paquetes. Una macro definida en su propio paquete puede simplemente usar símbolos internos en 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 ampliación de la UNTIL hace uso liberal del DO ()defmacro hasta ()expresión cuerpo cuerpo) `()do () (),expresión) ,@cuerpo) ;; macrolet establece el operador lexical vinculante para DO ()macrolet ()do ()...) ... algo más ...) ()hasta ()= ()al azar 10) 0) ()Write-line "Hola"))
La macro until
se expandirá en una forma que llama a do
que pretende referirse 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
puede no funcionar 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, que permite que las implementaciones lo diagnostiquen y lo rechacen.
Sistema de condiciones
El sistema de condiciones es responsable del manejo de excepciones en Common Lisp. Proporciona condiciones, controladors 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 controlador para este tipo de condición y llama al controlador. El controlador 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 provista como parte del objeto de condición, y llamar al apropiado función de reinicio.
Estos reinicios, si no están controlados por código, se pueden presentar a los usuarios (como parte de una interfaz de usuario, la de un depurador, por ejemplo), para 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), es posible una recuperación completa del error en muchos casos, donde otros sistemas de manejo de excepciones ya habrían terminado la rutina actual. El depurador también se puede personalizar o reemplazar usando la variable dinámica *debugger-hook*
. El código que se encuentra dentro de los formularios unwind-protect, como los finalizadores, también se ejecutará según corresponda a pesar de la excepción.
En el siguiente ejemplo (usando Symbolics Genera), el usuario intenta abrir un archivo en una función Lisp test llamada desde Read-Eval-Print-LOOP (REPL), cuando el archivo no existe. El sistema Lisp presenta cuatro reinicios. El usuario selecciona el reinicio Reintentar ABRIR usando un nombre de ruta diferente e ingresa un nombre de ruta 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. El sistema Lisp proporciona todo el código de manejo de errores y reinicio, que puede manejar y reparar el error sin terminar el código de usuario.
Comando: (prueba "conzippy confianzalispm-int.lisp")
Error: No se encontró el archivo.
Para lispm: Confzippy confianzalispm-int.lisp.newest
LMFS:OPEN-LOCAL-LMFS-1
Arg 0: #P"lispm: Confzippy confianzalispm-int.lisp.newest"
s-A, ■Resume confianza: Retry OPEN of lispm: confianzazippy confidenciallispm-int.lisp.newest
s-B: Retry OPEN utilizando un nombre de ruta diferente
s-C, ■Abort confianza: Volver a Lisp Top Level en un servidor TELNET
s-D: Restart process TELNET terminal
- Retry OPEN usando un nombre de ruta diferente
Use el nombre de ruta en su lugar [default lispm: confianzazippy confianzalispm-int.lisp.newest]:
lispm: Confzippy confianzalispm-init.lisp.newest
...el programa continúa
Sistema de Objetos Common Lisp (CLOS)
Common Lisp incluye un conjunto de herramientas para la programación orientada a objetos, el Sistema de Objetos Common Lisp o CLOS. Peter Norvig explica cuántos patrones de diseño son más simples de implementar en un lenguaje dinámico con las características de CLOS (herencia múltiple, mezclas, métodos múltiples, metaclases, combinaciones de métodos, etc.). Se ha propuesto que se incluyan varias extensiones de Common Lisp para la programación orientada a objetos en el estándar ANSI Common Lisp, pero finalmente se adoptó CLOS como el sistema de objetos estándar para Common Lisp. CLOS es un sistema de objetos dinámicos con despacho 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ámicos, CLOS permite cambios en tiempo de ejecución a funciones y clases genéricas. Los métodos se pueden agregar y eliminar, las clases se pueden agregar y redefinir, los objetos se pueden actualizar para cambios de clase y la clase de los objetos se puede cambiar.
CLOS se ha integrado en ANSI Common Lisp. Las funciones genéricas se pueden usar 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 de 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 podrían implementarse con CLOS. Estas posibilidades de uso adicionales de CLOS para ANSI Common Lisp no forman parte del estándar. Las implementaciones reales de Common Lisp usan CLOS para nombres de ruta, flujos, entrada-salida, condiciones, la implementación de CLOS en sí y más.
Compilador e intérprete
Un intérprete de Lisp ejecuta directamente el código fuente de Lisp proporcionado como objetos de Lisp (listas, símbolos, números,...) leídos desde expresiones s. Un compilador de Lisp genera bytecode o código de máquina a partir del código fuente de Lisp. Common Lisp permite compilar funciones Lisp individuales en la memoria y compilar archivos completos en código compilado almacenado externamente (archivos fasl).
Varias implementaciones de dialectos Lisp anteriores proporcionaron tanto un intérprete como un compilador. Desafortunadamente, a menudo la semántica era diferente. Estos Lisps anteriores implementaron 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 usen el alcance léxico por defecto. El estándar Common Lisp describe tanto la semántica del intérprete como la del compilador. Se puede llamar al compilador usando la función compilar para funciones individuales y usando la función compilar-archivo 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 esta última, se pueden dar valores entre 0 (no importante) y 3 (más importante) a varias cualidades de optimización: velocidad, espacio, seguridad, depuración y velocidad de compilación.
También hay una función para evaluar código Lisp: eval
. eval
toma el código como expresiones S analizadas previamente y no, como en otros lenguajes, como cadenas de texto. De esta manera, el código se puede construir con las funciones habituales de Lisp 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) están implementando eval
usando su compilador. De esta manera se compila el código, aunque se evalúa mediante la función eval
.
El compilador de archivos se invoca usando la función compilar-archivo. El archivo generado con el código compilado se denomina archivo fasl (de carga rápida). Estos archivos fasl y también los archivos de código fuente se pueden cargar con la función cargar en un sistema Common Lisp en ejecución. Dependiendo de la implementación, el compilador de archivos genera código de bytes (por ejemplo, para la máquina virtual de Java), código de lenguaje C (que luego se compila con un compilador de C) o, directamente, código nativo.
Las implementaciones de Common Lisp se pueden usar de forma interactiva, aunque el código se compile por completo. La idea de un lenguaje interpretado, por lo tanto, no se aplica al 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 aún no estaba vinculada, mientras que defparameter
siempre realizará la asignación. Esta distinción es útil al evaluar, compilar y cargar código de forma interactiva en una imagen en vivo.
También se proporcionan algunas características 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 de usuario. El operador especial progv
permite crear enlaces léxicos programáticamente, mientras que los paquetes también son manipulables. El compilador Lisp está disponible en tiempo de ejecución para compilar archivos o funciones individuales. Estos facilitan el uso de Lisp como un compilador o intérprete intermedio para otro idioma.
Ejemplos de código
Paradoja del cumpleaños
El siguiente programa calcula el menor número de personas en una habitación para las que la probabilidad de cumpleaños únicos es inferior al 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.
Por convención, las constantes en Common Lisp se encierran con caracteres +.
()defconstant +año+ 365)()defun Paradoja de cumpleaños ()probabilidad Número de personas) ()Deja ()Nueva probabilidad ()* ()/ ()- +año+ Número de personas) +año+) probabilidad)) ()si (). Nueva probabilidad 0.5) ()1+ Número de personas) ()Paradoja de cumpleaños Nueva probabilidad ()1+ Número de personas))))
Llamar a la función de ejemplo usando REPL (Read Eval Print Loop):
CL-USER ó (birthday-paradox 1.0 1)
23
Ordenar una lista de objetos de persona
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 objetos person
.
Luego iteramos sobre la lista ordenada.
()defclass persona () ()Nombre :initarg :nombre :accesor nombre de persona) ()Edad :initarg :age :accesor persona y edad) ():documentación "La persona de clase con ranuras NAME y AGE.")()defmethod pantalla ()objeto persona) streaming) "Displaying a PERSON object to an output stream." ()con lotes ()Nombre Edad) objeto ()formato streaming "~a (~a)" Nombre Edad))()defparameter *group* ()lista ()make-instance 'persona :nombre "Bob" :age 33) ()make-instance 'persona :nombre "Chris" :age 16) ()make-instance 'persona :nombre "Ash" :age 23) "Una lista de objetos personales".)()lista ()persona ()especie ()copy-list *group*) # '■ :key # 'persona y edad) ()pantalla persona *standard-output*) ()terpri)
Imprime los tres nombres con edad descendente.
Bob (33)
Ash (23)
Chris (16)
Exponenciar al cuadrado
Se demuestra el uso de la macro LOOP:
()defun poder ()x n) ()bucle con resultado = 1 mientras ()plusp n) cuando ()extraño n) do ()setf resultado ()* resultado x) do ()setf x ()* x x) n ()truncate n 2) finalmente ()retorno resultado))
Ejemplo de uso:
CL-USER ■ ()poder 2 200)16069380442589902755419620923411626025222993782792835301376
Compare con la potenciación incorporada:
CL-USER ■ ()= ()Gastos 2 200) ()poder 2 200)T
Encuentra la lista de proyectiles disponibles
WITH-OPEN-FILE es una macro que abre un archivo y proporciona una transmisión. Cuando el formulario regresa, el archivo se cierra automáticamente. FUNCALL llama a un objeto de función. El LOOP recopila todas las líneas que coinciden con el predicado.
()defun list-matching-lines ()archivo predicar) "Retorna una lista de líneas en archivo, para las cuales el predicado solicitó la línea devuelve T." ()con perfil abierto ()streaming archivo) ()bucle para línea = ()read-line streaming Nil Nil) mientras línea cuando ()funcall predicar línea) cobro es))
La función AVAILABLE-SHELLS llama a la función anterior LIST-MATCHING-LINES con un nombre de ruta y una función anónima como predicado. El predicado devuelve el nombre de ruta de un shell o NIL (si la cadena no es el nombre de archivo de un shell).
()defun conchas disponibles ()profesionales ()archivo #p"/etc/shells") ()list-matching-lines archivo ()lambda ()línea) ()y ()plusp ()longitud línea) ()char= ()char línea 0) #) ()pathname ()cuerda-derecha-trim '()#space #tab) línea)))))
Resultados de ejemplo (en Mac OS X 10.6):
CL-USER ■ ()conchas disponibles)()#P"/bin/bash" #P"/bin/csh" #P"/bin/ksh" #P"/bin/sh #P"/bin/tcsh" #P"/bin/zsh")
Comparación con otros Lisps
Common Lisp se compara y contrasta con mayor frecuencia con Scheme, aunque solo sea porque son los dos dialectos de Lisp más populares. Scheme es anterior a CL, y proviene no solo de la misma tradición de Lisp, sino de algunos de los mismos ingenieros: Guy Steele, con quien Gerald Jay Sussman diseñó Scheme, presidió el comité de estándares de Common Lisp.
Common Lisp es un lenguaje de programación de propósito general, en contraste con las variantes de Lisp como Emacs Lisp y AutoLISP, que son lenguajes de extensión integrados en productos particulares (GNU Emacs y AutoCAD, respectivamente). A diferencia de muchos Lisps anteriores, Common Lisp (como Scheme) usa un alcance de variable léxica por defecto 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, utilizaron 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 alcance dinámico, pero deben declararse explícitamente como "especial". No hay diferencias en el ámbito entre los intérpretes y compiladores de ANSI CL.
Common Lisp a veces se denomina Lisp-2 y Scheme un 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 etiquetas go, nombres de bloque y palabras clave loop
). Existe una controversia de larga data entre CL y los defensores de Scheme sobre las compensaciones involucradas en múltiples espacios de nombres. En Scheme, es (en general) necesario evitar dar nombres de variables que entren en conflicto con las funciones; Las funciones de esquema suelen tener argumentos denominados lis
, lst
o lyst
para no entrar en conflicto con la función del sistema list
. Sin embargo, en CL es necesario referirse explícitamente al espacio de nombres de la función cuando se pasa una función como argumento, lo cual también es una ocurrencia común, como en el ejemplo anterior de sort
.
CL también se diferencia de Scheme en el manejo de los 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, con NIL también para 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 tanto como predicados (respondiendo a una pregunta de valor booleano) como devolviendo 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 del Esquema requieren una optimización de llamada final, lo que no hace el estándar CL. La mayoría de las implementaciones de CL ofrecen optimización de llamada final, aunque a menudo solo cuando el programador usa una directiva de optimización. No obstante, el estilo de codificación común de CL no favorece el uso ubicuo de la recursión que prefiere el estilo de Scheme: lo que un programador de Scheme expresaría con la recursión de cola, un usuario de CL normalmente lo expresaría con una expresión iterativa en do
, dolist
, loop
, o (más recientemente) con el paquete iterate
.
Implementaciones
Vea la categoría Implementaciones de Common Lisp.
Common Lisp se define por una especificación (como Ada y C) en lugar de una implementación (como Perl). Hay muchas implementaciones y las áreas de detalles estándar en las que pueden diferir válidamente.
Además, las implementaciones suelen venir con extensiones, que brindan una funcionalidad que no cubre el estándar:
- Top-Level Interactivo (REPL)
- Colección de basura
- Debugger, Stepper and Inspector
- Estructuras de datos débiles
- Secuencias extensibles
- LOOP extensible
- Medio ambiente
- CLOS Protocolo de metaobjeto
- Corrientes extensibles basadas en CLOS
- Sistema de condiciones basados en CLOS
- Transmisiones de redes
- CLOS persistentes
- Soporte para Unicode
- Interfaz de lengua extranjera (a menudo a C)
- Interfaz del sistema operativo
- Java Interface
- Panes y Multiprocesamiento
- Entrega de aplicaciones (aplicaciones, bibliotecas dinámicas)
- Ahorro de imágenes
Se han creado bibliotecas de software gratuito y de código abierto para admitir extensiones de Common Lisp de forma portátil, y se encuentran sobre todo en los repositorios de los proyectos Common-Lisp.net y CLOCC (Common Lisp Open Code Collection).
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 admitir compiladores incrementales, compiladores de archivos y compiladores de bloques. Las declaraciones estándar para optimizar la compilación (como funciones insertadas o 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 en código de máquina nativo. Algunas implementaciones pueden crear aplicaciones independientes (optimizadas). Otros compilan en bytecode interpretado, que es menos eficiente que el código nativo, pero facilita la portabilidad del código binario. Algunos compiladores compilan código Common Lisp en código C. La idea errónea de que Lisp es un lenguaje puramente interpretado se debe probablemente a que 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 es ampliamente utilizada.
Algunas implementaciones basadas en Unix (CLISP, SBCL) se pueden usar como lenguaje de secuencias de comandos; es decir, invocado por el sistema de forma transparente en la forma en que lo es un intérprete de shell de Perl o Unix.
Lista de implementaciones
Implementaciones comerciales
- Allegro Common Lisp
- para Microsoft Windows, FreeBSD, Linux, Apple macOS y varias variantes UNIX. Allegro CL proporciona un entorno de desarrollo integrado (IDE) (para Windows y Linux) y amplias capacidades para la entrega de aplicaciones.
- Lisp líquido común
- antiguamente llamada Lucid Common Lisp. Sólo mantenimiento, sin nuevas liberaciones.
- LispWorks
- para Microsoft Windows, FreeBSD, Linux, Apple macOS, iOS, Android y varias variantes UNIX. LispWorks proporciona un entorno de desarrollo integrado (IDE) (disponible para la mayoría de las plataformas, pero no para iOS y Android) y amplias capacidades para la entrega de aplicaciones.
- mocl
- para iOS, Android y macOS.
- Open Genera
- por DEC Alpha.
- Scieneer Common Lisp
- que está diseñado para la computación científica de alto rendimiento.
Implementaciones libremente redistribuibles
- Lisp común de oso armado (ABCL)
- Una implementación de CL que se ejecuta en la máquina virtual de Java. Incluye un compilador a código byte Java, y permite el acceso a bibliotecas Java de CL. Anteriormente era sólo un componente del Editor J del Oso Armado.
- Clasp
- Una implementación basada en LLVM que interopera perfectamente con bibliotecas C++. Funciona en varios sistemas Unix y Unix (incluyendo macOS).
- CLISP
- Una implementación de compilación de bytecode, portátil y funciona en varios sistemas Unix y Unix (incluyendo macOS), así como Microsoft Windows y varios otros sistemas.
- Clozure CL (CCL)
- Originalmente un tenedor libre y de código abierto de Macintosh Common Lisp. Como implica la historia, CCL fue escrito para Macintosh, pero Clozure CL ahora funciona en macOS, FreeBSD, Linux, Solaris y Windows. 32 y 64 bits x86 puertos se soportan en cada plataforma. Además hay puertos Power PC para Mac OS y Linux. CCL era anteriormente conocido como OpenMCL, pero ese nombre ya no se utiliza, para evitar confusión con la versión de código abierto de Macintosh Common Lisp.
- CMUCL
- Originalmente de la Universidad Carnegie Mellon, ahora mantenido como software libre y de código abierto por un grupo de voluntarios. CMUCL utiliza un compilador rápido de código nativo. Está disponible en Linux y BSD para Intel x86; Linux para Alpha; macOS para Intel x86 y PowerPC; y Solaris, IRIX y HP-UX en sus plataformas nativas.
- Corman Common Lisp
- para Microsoft Windows. En enero de 2015 Corman Lisp ha sido publicado bajo licencia MIT.
- Embeddable Common Lisp (ECL)
- ECL incluye un intérprete y compilador de códigos bytecode. También puede compilar código Lisp a código de máquina a través de un compilador C. ECL entonces compila Código Lisp a C, compila el código C con un compilador C y luego puede cargar el código de máquina resultante. También es posible incrustar ECL en programas C, y código C en programas Common Lisp.
- GNU Common Lisp (GCL)
- El compilador Lisp del Proyecto GNU. Aún no totalmente compatible con ANSI, GCL es sin embargo la implementación de la elección para varios grandes proyectos incluyendo las herramientas matemáticas Maxima, AXIOM y (históricamente) ACL2. GCL funciona en Linux bajo once arquitecturas diferentes, y también bajo Windows, Solaris y FreeBSD.
- Macintosh Common Lisp (MCL)
- Versión 5.2 para ordenadores Apple Macintosh con un procesador PowerPC ejecutando Mac OS X es de código abierto. RMCL (basado en MCL 5.2) funciona en ordenadores Apple Macintosh basados en Intel usando el traductor binario de Rosetta de Apple.
- ManKai Common Lisp (MKCL)
- Una rama del ECL. MKCL hace hincapié en la fiabilidad, la estabilidad y la calidad general del código a través de un sistema de tiempos de ejecución altamente reelaborado, nativomente multitejido. En Linux, MKCL cuenta con un sistema de tiempo de ejecución totalmente compatible con POSIX.
- Movitz
- Implementa un entorno Lisp para computadoras x86 sin depender de ningún sistema operativo subyacente.
- Poplog
- Poplog implementa una versión de CL, con POP-11, y opcionalmente Prolog, y Standard ML (SML), permitiendo la programación de lenguaje mixto. Para todos, el idioma de aplicación es POP-11, que se compila de forma incremental. También tiene un editor integrado parecido a Emacs que se comunica con el compilador.
- Steel Bank Common Lisp (SBCL)
- Una rama de CMUCL. "En términos generales, SBCL se distingue de CMU CL por un mayor énfasis en la sostenibilidad". SBCL funciona en las plataformas que CMUCL lo hace, excepto HP/UX; además, funciona en Linux para AMD64, PowerPC, SPARC, MIPS, Windows x86 y tiene soporte experimental para ejecutar en Windows AMD64. SBCL no utiliza un intérprete por defecto; todas las expresiones se compilan a código nativo a menos que el usuario cambie el intérprete. El compilador SBCL genera código nativo rápido según una versión anterior de The Computer Language Benchmarks Game.
- Ufasoft Common Lisp
- port of CLISP for windows platform with core written in C++.
Otras implementaciones
- Austin Kyoto Common Lisp
- una evolución de Kioto Common Lisp por Bill Schelter
- Mariposa común Lisp
- una aplicación escrita en el esquema para el ordenador multiprocesador BBN Butterfly
- CLICC
- a Common Lisp a C compilador
- CLOE
- Lisp común para PCs por Symbolics
- Codemist Common Lisp
- utilizado para la versión comercial del sistema de álgebra del ordenador Axiom
- ExperCommon Lisp
- una aplicación temprana para el Macintosh de Apple por ExperTelligence
- Golden Common Lisp
- una implementación para el PC por GoldHill Inc.
- Ibuki Common Lisp
- una versión comercializada de Kyoto Common Lisp
- Kyoto Common Lisp
- el primer compilador Common Lisp que utilizó C como idioma objetivo. GCL, ECL y MKCL se originan de esta aplicación Common Lisp.
- L
- una pequeña versión de Common Lisp para sistemas integrados desarrollados por IS Robotics, ahora iRobot
- Lisp Machines (de Symbolics, TI y Xerox)
- proporcionó implementaciones de Lisp Común además de su dialecto nativo de Lisp (Lisp Machine Lisp o Interlisp). CLOS también estaba disponible. Symbolics proporciona una versión mejorada Common Lisp.
- Procyon Common Lisp
- una implementación para Windows y Mac OS, utilizado por Franz para su puerto de Windows de Allegro CL
- Star Sapphire Common LISP
- una aplicación para el PC
- SubL
- una variante de Lisp Común utilizada para la implementación del sistema basado en conocimientos Cyc
- Nivel superior Lisp común
- una aplicación temprana para la ejecución concurrente
- WCL
- a Aplicación de bibliotecas compartidas
- VAX Common Lisp
- Implementación de la Corporación de Equipos Digitales que corrió en sistemas VAX ejecutando VMS o ULTRIX
- XLISP
- una implementación escrita por David Betz
Aplicaciones
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 usa en muchas aplicaciones comerciales, incluido Yahoo! Sitio de comercio web de la tienda, que originalmente involucró a Paul Graham y luego fue reescrito en C++ y Perl. Otros ejemplos notables incluyen:
- ACT-R, una arquitectura cognitiva utilizada en un gran número de proyectos de investigación.
- Authorizer's Assistant, a large rule-based system used by American Express, analyzing credit requests.
- Cyc, un proyecto de larga duración para crear un sistema basado en el conocimiento que proporciona una gran cantidad de conocimiento de sentido común.
- Gensym G2, un sistema experto en tiempo real y un motor de reglas de negocio
- Genworks GDL, basado en el núcleo Gendl de código abierto.
- El entorno de desarrollo para el desarrollo Jak y Daxter series de videojuegos, desarrolladas por Naughty Dog.
- ITA Motor de búsqueda de tarifas bajas del software, utilizado por sitios web de viajes como Orbitz y Kayak.com y aerolíneas como American Airlines, Continental Airlines y US Airways.
- Mirai, una suite gráfica 3D. Fue usado para animar la cara de Gollum en la película Señor de los anillos: Las dos torres.
- Opusmodus es un sistema de composición musical basado en Common Lisp, utilizado en composición asistida por ordenador.
- Sistema de verificación de prototipos (PVS), un entorno mecanizado para la especificación y verificación formales.
- PWGL es un sofisticado entorno de programación visual basado en Common Lisp, utilizado en composición asistida por ordenador y síntesis de sonido.
- Piano, una suite completa de análisis de aviones, escrita en Common Lisp, utilizada por empresas como Boeing, Airbus y Northrop Grumman.
- Gramáticamente, una plataforma de escritura en inglés, tiene su motor de gramática básica escrito en Common Lisp.
- The Dynamic Analysis and Replanning Tool (DART), which is said to alone have paid back during the years from 1991 to 1995 for all thirty years of DARPA investments in AI research.
- Jet Propulsion Lab de la NASA "Deep Space 1", un galardonado programa de Lisp Común para autopilotar la nave espacial Deep Space One.
- SigLab, una plataforma de Lisp Común para el procesamiento de señales utilizada en la defensa de misiles, construida por Raytheon.
- Sistema de Planificación Misional Mars Pathfinder de la NASA.
- SPIKE, un sistema de programación para los observatorios y satélites terrestres o espaciales, en particular el Telescopio Espacial Hubble, escrito en Common Lisp.
- Common Lisp ha sido utilizado para prototipar el colector de basura de Microsoft. NET Common Language Runtime.
- La versión original de Reddit, aunque los desarrolladores cambiaron más tarde a Python debido a la falta de bibliotecas para Common Lisp, de acuerdo con un blog oficial del cofundador de Reddit Steve Huffman.
También existen aplicaciones de código abierto escritas en Common Lisp, como:
- ACL2, un probador de teorema automatizado completo para una variante de aplicación de Common Lisp.
- Axiom, un sofisticado sistema de álgebra de ordenador.
- Maxima, un sofisticado sistema de álgebra informática, basado en Macsyma.
- OpenMusic, un ambiente de programación visual orientado a objetos basado en Common Lisp, utilizado en la composición asistida por ordenador.
- Pgloader, un cargador de datos para PostgreSQL, que fue re-escrito de Python a Common Lisp.
- Stumpwm, un tiling, teclado impulsado X11 Window Manager escrito enteramente en Common Lisp.
Contenido relacionado
Caché (informática)
Tim Berners-Lee
Troff