Hace algún tiempo (unos tres años) decidí leer un libro de texto sobre Lisp. Sin ningún propósito específico, solo por el bien del desarrollo general y la capacidad de sorprender a los interlocutores con exóticos (una vez que parece, incluso funcionó).
Pero después de una inspección más cercana, Lisp resultó ser realmente poderoso, flexible y, curiosamente, útil en la vida cotidiana. Todas las tareas de automatización menores migraron rápidamente a los scripts en Lisp, y también hubo oportunidades para la automatización de las más complejas.
Cabe señalar aquí que por "capacidad de automatización" me refiero a una situación en la que el tiempo total para escribir y depurar un programa es menor que el tiempo dedicado a resolver manualmente la misma tarea.
Paul Graham ha escrito más de un artículo e incluso un libro sobre los beneficios de Lisp. En el momento de escribir este artículo, Lisp ocupa el puesto 33 en el ranking TOIBE (tres veces muerto que Delphi muerto). Surge la pregunta: ¿por qué el lenguaje es tan pequeño si es tan conveniente? Alrededor de dos años de uso dieron algunos indicios de las razones.
Desventajas
1. estructuras de datos compartidosUn concepto que le permite optimizar programas funcionales, pero lleno de errores sutiles en imperativo. La posibilidad de daño accidental a una estructura de datos extraña cuando se modifica una variable que no tiene una conexión visible con la estructura requiere que el programador monitoree constantemente lo que sucede detrás de escena y conozca la implementación interna de cada función utilizada (sistema y usuario). Lo más sorprendente es la capacidad de dañar el cuerpo de una función modificando su valor de retorno.
2. Falta de encapsulaciónAunque existe el concepto de un paquete, no tiene nada que ver con el
paquete en Ada o la
unidad en Delphi. Cualquier código puede agregar cualquier cosa a cualquier paquete (excepto los del sistema). Cualquier código puede extraer cualquier cosa de cualquier paquete usando el operador
:: .
3. Abreviaturas peligrosas¿Cuál es la diferencia entre MAPCAN y MAPCON? ¿Por qué en SETQ, la última letra Q? Dada la edad del idioma, puede comprender las razones de este estado de cosas, pero quiero que el idioma sea un poco más limpio.
4. MultithreadingEste inconveniente está indirectamente relacionado con Lisp y se refiere principalmente a la implementación que uso: SteelBank Common Lisp. Common Lisp no admite subprocesos múltiples. Un intento de usar la implementación proporcionada por SBCL falló.
Es una pena rechazar una herramienta tan conveniente, pero la insatisfacción se acumula gradualmente.
Busca una solución
Primero puedes ir a Wikipedia en la página de Lisp. Inspeccione la sección "Dialectos". Lea una breve introducción a cada uno. Y tenga en cuenta que el sabor y el color de todos los marcadores son diferentes.
Si quieres hacer algo, debes hacerlo tú mismo
- Jean Baptiste Emmanuel Sorg
Intentemos crear nuestro propio Lisp correcto agregando un poco de Ada, una gran cantidad de Delphi y una gota de Oberon. Llamamos a la mezcla resultante Fox.
Conceptos basicos
1. Sin punterosEn la lucha contra el PROBLEMA-1, todas las operaciones deben llevarse a cabo copiando los valores. Por el tipo de estructura de datos en el código o al imprimir, todas sus propiedades, conexiones externas e internas deben ser totalmente visibles.
2. Agregar módulosComo parte de la lucha contra el problema 2, importamos
paquetes , y
utilizamos declaraciones de Ada. En el proceso, descartamos el esquema de importación / sombreado excesivamente complejo para los símbolos Lisp.
(package - ( ) () ())
(with -)
(use -)
3. Menos abreviaturasLos caracteres más comunes y comunes se seguirán abreviando, pero sobre todo los más obvios:
const ,
var . Función de salida formateada: FMT requiere reducción, ya que a menudo se encuentra dentro de las expresiones.
Elt , tomando un elemento, se filtró de Common Lisp y echó raíces, aunque no hay necesidad de reducirlo.
4. Identificadores que no distinguen entre mayúsculas y minúsculasCreo que el lenguaje correcto (y el sistema de archivos) {$ HOLYWAR +} no distingue entre mayúsculas y minúsculas {$ HOLYWAR-} para no volver a enloquecer sus cerebros.
5. Facilidad de uso con la distribución del teclado rusoLa sintaxis Lisi de todas las formas posibles evita el uso de caracteres que no están disponibles en uno de los diseños. No hay llaves cuadradas o rizadas. No #, ~, &, <,>, |. Al leer literales numéricos, tanto una coma como un punto se consideran separadores decimales.
6. Alfabeto extendidoUna de las cosas buenas de SBCL fue UTF-8 en el código. La capacidad de declarar las constantes BEAR, VODKA y BALALAYKA simplifica enormemente la escritura del código de la aplicación. La capacidad de insertar Ω, Ψ y Σ hace que las fórmulas en el código sean más visuales. Aunque, en teoría, existe la posibilidad de utilizar cualquier carácter Unicode, es difícil garantizar la exactitud de trabajar con ellos (más bien pereza que dificultad). Nos limitamos al cirílico, latín y griego.
7. Literales numéricosEsta es la extensión de lenguaje más útil para mí.
10_000
La última opción me parece la más no estética, pero es la más popular.
8. CiclosLos ciclos en Lisp no son estándar y son bastante desordenados. Simplifique al conjunto estándar mínimo.
(for i 5
La variable de bucle no es visible afuera.
(while )
9. GOTONo es un operador muy necesario, pero sin él es difícil demostrar el descuido de las reglas de la programación estructural.
(block : (goto :))
10. Unificación del alcanceHay dos tipos diferentes de alcance en Lisp: TOPLEVEL y local. En consecuencia, hay dos formas diferentes de declarar variables.
(defvar A 1) (let ((a 1)) …)
En Fox solo se utiliza un método tanto en el nivel superior del script como en las áreas locales, incluidos los paquetes.
(var A 1)
Si desea limitar el alcance, use el operador
(block (var A 1) (set A 2) (fmt nil A))
El cuerpo del bucle está contenido en la instrucción BLOCK implícita (como el cuerpo de la función / procedimiento). Todas las variables declaradas en el bucle se destruyen al final de la iteración.
11. Caracteres de una ranuraEn Lisp, las funciones son objetos especiales y se almacenan en una ranura de símbolo especial. Un solo carácter puede almacenar simultáneamente una lista de variables, funciones y propiedades. En un zorro, cada personaje está asociado con un solo significado.
12. ELT convenienteEl acceso típico a un elemento de estructura compleja en Lisp se ve así
(elt (slot-value (elt 1) '-2) 3)
Fox implementa un operador ELT unificado que proporciona acceso a elementos de cualquier tipo compuesto (listas, cadenas, registros, conjuntos de bytes, tablas hash).
(elt 1 \-2 3)
La funcionalidad idéntica también se puede obtener con una macro en Lisp
(defmacro field (object &rest f) " . (field *object* 0 :keyword symbol \"string\") . plist. ( ) . ." (if f (symbol-macrolet ((f0 (elt f 0))(rest (subseq f 1))) (cond ((numberp f0) `(field (elt ,object ,f0) ,@rest)) ((keywordp f0) `(field (getf ,object ,f0) ,@rest)) ((stringp f0) `(field (cdr (assoc ,f0 ,object :test 'equal)) ,@rest)) ((and (listp f0) (= 2 (length f0))) `(field (,(car f0) ,(cadr f0) ,object) ,@rest)) ((symbolp f0) `(field (,f0 ,object) ,@rest)) (t `(error " ")))) object))
13. Restricción de los modos de transferencia de parámetros de subrutinaHay al menos cinco modos de transferencia de parámetros en Lisp: obligatorio,
y opcional ,
y resto ,
y clave ,
y todo, y se permite su combinación arbitraria. De hecho, la mayoría de las combinaciones dan efectos extraños.
En Fox está permitido usar solo una combinación de los parámetros requeridos y uno de los siguientes modos para elegir
: key ,: opcional ,: flag ,: rest .
14. MultithreadingPara simplificar al máximo la escritura de programas multiproceso, se adoptó el concepto de separación de memoria. Cuando se genera un hilo, se copian todas las variables disponibles para el nuevo hilo. Todas las referencias a estas variables se reemplazan por referencias a copias. La transferencia de información entre flujos solo es posible a través de objetos protegidos o mediante el resultado devuelto por el flujo al finalizar.
Los objetos protegidos siempre contienen secciones críticas para garantizar operaciones atómicas. Iniciar sesión en secciones críticas es automático: no hay operadores separados para esto en el idioma. Los objetos protegidos incluyen: cola de mensajes, consola y descriptores de archivo.
La creación de hilos es posible con una función de visualización multiproceso
(map-th (function (x) …) --)
Map-th inicia automáticamente la cantidad de subprocesos igual a la cantidad de procesadores en el sistema (o el doble si tiene Intel dentro). En una llamada recursiva, las llamadas de mapeo posteriores funcionan en un solo hilo.
Además, hay una función de subproceso incorporada que ejecuta un procedimiento / función en un subproceso separado.
15. Limpieza funcional en el código imperativo.El Fox tiene funciones para programación funcional y procedimientos para procedimientos. Las rutinas declaradas usando la palabra clave de función están sujetas a los requisitos de ausencia de efectos secundarios y la independencia del resultado de factores externos.
No realizado
Algunas características interesantes de Lisp no se cumplieron debido a la baja prioridad.
1. Métodos generalizadosCapacidad para sobrecargar funciones con defgeneric / defmethod.
2. Herencia3. depurador incorporadoCuando ocurre una excepción, el intérprete de Lisp cambia al modo de depuración.
4. UFFIInterfaz para conectar módulos escritos en otros idiomas.
5. BIGNUMSoporte arbitrario de profundidad de bits
DescartadoAlgunas de las características de Lisp han sido consideradas y consideradas inútiles / dañinas.
1. Combinación guiada de métodos.Cuando se llama a un método para una clase, se realiza una combinación de métodos principales y es posible cambiar las reglas de combinación. El comportamiento final del método parece poco predecible.
2. ReiniciaEl controlador de excepciones puede realizar cambios en el estado del programa y enviar un comando de reinicio al código que generó la excepción. El efecto de la aplicación es similar al uso del operador GOTO para cambiar de una función a otra.
3. La cuenta romanaLisp admite el sistema de números, que está desactualizado poco antes de su aparición.
Uso
Aquí hay algunos ejemplos de código simple.
(function crc8 (data :optional seed) (var result (if-nil seed 0)) (var s_data data) (for bit 8 (if (= (bit-and (bit-xor result s_data) $01) 0) (set result (shift result -1 8)) (else (set result (bit-xor result $18)) (set result (shift result -1 8)) (set result (bit-or result $80)))) (set s_data (shift s_data -1 8))) result)
Implementación
El intérprete está escrito en Delphi (FreePascal en modo de compatibilidad). Está construido en Lazarus 1.6.2 y superior, bajo Windows y Linux de 32 y 64 bits. De las dependencias externas, requiere libmysql.dll. Contiene aproximadamente 15_000..20_000 líneas. Hay alrededor de 200 funciones integradas para diversos fines (algunas se sobrecargan ocho veces).
Almacenado aquíEl soporte para la tipificación dinámica se realiza de manera trivial: todos los tipos de datos procesados están representados por los herederos de una clase TValue.
El tipo más importante para Lisp: la lista es, como es habitual en Delphi, una clase que contiene una matriz dinámica de objetos de tipo TValue. Para este tipo, se implementa el mecanismo CopyOnWrite.
La gestión de la memoria es automática basada en el recuento de referencias. Para estructuras recursivas, todos los enlaces en la estructura se cuentan simultáneamente. La liberación de memoria comienza inmediatamente cuando las variables salen del ámbito. No existen mecanismos para el inicio retrasado del recolector de basura.
El manejo de excepciones funciona en un mecanismo integrado en Delphi. Por lo tanto, los errores que ocurren en el código del intérprete pueden ser procesados por el código ejecutable en el Fox.
Cada operador o función Lisi incorporada se implementa como un método o función en el código del intérprete. La secuencia de comandos se ejecuta mediante una llamada recursiva mutuamente de implementaciones. El código del intérprete y el script tienen una pila de llamadas común.
Las variables de script se almacenan en la memoria dinámica de forma independiente. Cada función definida por el usuario tiene su propia pila para almacenar referencias variables, independientemente de la pila de nivel superior o la pila de funciones principales.
De particular dificultad es la implementación del operador de asignación (conjunto) para elementos estructurales. El cálculo directo del puntero al elemento requerido conlleva el riesgo de colgar enlaces, ya que la sintaxis de Lisi no prohíbe modificar la estructura durante el cálculo del elemento requerido. Como solución de compromiso, se implementa un "puntero de cadena": un objeto que contiene una referencia a una variable y una matriz de índices numéricos para indicar la ruta dentro de la estructura. Tal puntero también es susceptible al problema de los enlaces colgantes, pero en caso de falla genera un mensaje de error significativo.
Herramientas de desarrollo
1. Consola2. Editor de textoEquipado con resaltado de sintaxis y la capacidad de ejecutar un script editable en F9.

Conclusión
En el estado actual, el proyecto resuelve los problemas para los que fue concebido y no requiere un mayor desarrollo activo. Muchas de las imperfecciones presentes no afectan significativamente el trabajo.