Como es 2023, obviamente estoy aprendiendo Common Lisp
(log.schemescape.com)- En el marco de pensar en lenguajes que puedan seguir usándose en el futuro, esta elección tiene menos que ver con la practicidad y más con experimentar directamente con un flujo de desarrollo antiguo pero moderno
- Janet es un lenguaje de scripting pequeño de la familia Lisp/Clojure que apunta a un espacio similar al de Lua, y llamó la atención por su biblioteca estándar y su metaprogramación basada en macros
- Ventajas aparentes de Janet como la portabilidad, la posibilidad de embeberlo y las Parsing Expression Grammars tenían una relación limitada con las necesidades actuales, y alternativas existentes como CHICKEN Scheme, CLISP, SBCL y Packrat parecían suficientes
- El punto clave del desarrollo guiado por REPL de Common Lisp es que, desde un estado de error en tiempo de ejecución, se puede inspeccionar la pila, las variables y los datos vivos, modificarlos y luego reanudar la ejecución interrumpida
- Aunque aprender un nuevo lenguaje y su biblioteca estándar tiene un costo, se consideró que valía la pena probar directamente este flujo de trabajo en un entorno donde fue incorporado al diseño desde el inicio
Por qué el interés pasó de Janet a Common Lisp
- Venía pensando en future-proof programming languages porque quería escribir código que pudiera seguir usando en el futuro, pero al crear algo para compartir adopto una postura pragmática: elegir un lenguaje popular, portable y cómodo
- Al mismo tiempo, también exploro otros lenguajes y herramientas de programación por diversión y experimentación
- Hace poco, al encontrar el libro gratuito Janet for Mortals, surgió mi interés por Janet
- Janet es un lenguaje de scripting relativamente pequeño inspirado en Lisp/Clojure, que apunta a un nicho similar al de Lua
- Ofrece una biblioteca estándar real, metaprogramación al estilo Lisp y ejecución en tiempo de compilación mediante macros
- Lo que más me atrajo de Janet, además de su carácter Lisp en general, fue su portabilidad, la posibilidad de embeberlo y las Parsing Expression Grammars
- Como actualmente no necesito embeber un lenguaje, esa posibilidad no fue una razón decisiva
- En cuanto a portabilidad, CHICKEN Scheme, CLISP y Steel Bank Common Lisp también parecían buenas opciones
- Para parsing, Packrat parecía una elección razonable
- Al final, quedaron menos razones para elegir Janet en lugar de un Lisp existente
El flujo guiado por REPL que me hizo aprender Common Lisp
- Un artículo sobre el flujo de trabajo guiado por REPL, casi exclusivo de Common Lisp, se convirtió en la motivación para aprenderlo
- El ejemplo es una situación en la que la función
foollama a una funciónbarque aún no está definida, y se produce un error durante la ejecución defoo- En entornos Lisp antiguos o Smalltalk, la interrupción de
foolleva a un breakloop - El breakloop es un REPL completo dentro del entorno dinámico de la función interrumpida
- Se puede recorrer la pila de llamadas interrumpida y revisar las variables visibles en cada stack frame
- Se pueden inspeccionar y modificar los datos vivos del programa en ejecución
- Si la causa es una variable o un valor de campo incorrecto, se puede cambiar el valor y luego reanudar la función interrumpida
- Dentro del REPL se puede definir la función
barfaltante y reanudarfoopara obtener un resultado razonable
- En entornos Lisp antiguos o Smalltalk, la interrupción de
- Ya había visto varias funciones de “edit and continue”, pero me resultó novedoso que Common Lisp haya sido creado teniendo en mente este tipo de flujo de trabajo
- Mi experiencia de depuración ya había pasado de
printf debuggingal uso de depuradores realesprintf debuggingconsiste en insertar temporalmente código de logs, recompilar y revisar el resultado de la ejecución- En situaciones donde no se puede modificar o volver a ejecutar el programa, tuve que aprender a usar un depurador
- Un depurador real es la herramienta correcta, pero configurar el entorno de depuración puede ser doloroso o, a veces, imposible
- Common Lisp parecía un entorno que lleva la depuración un paso más allá
- Ya había tenido experiencias modificando memoria desde un depurador para probar posibles arreglos
- Pero la posibilidad de reescribir y parchear código dentro de un proceso en ejecución resultaba especialmente atractiva
- El objetivo de aprender Common Lisp es comprobar directamente si construir programas de forma interactiva con este enfoque es una manera agradable de trabajar
- No está claro si aprender un lenguaje nuevo y su biblioteca estándar solo para explorar un flujo de trabajo nuevo sea la mejor decisión, pero tampoco hay una alternativa claramente mejor
- Otros lenguajes pueden tener integraciones similares entre REPL y editor
- Sin embargo, considero que esas funciones podrían tener más bugs porque no fueron soportadas desde el inicio, sino agregadas después
- Aunque no quede satisfecho, quedará el hecho de haberlo intentado en las mejores condiciones, con las herramientas estándar
- Los recursos que ayudaron a aprender Common Lisp están recopilados en otro artículo, Common Lisp resources
1 comentarios
Opiniones de Hacker News
Hasta ahora, lo que me ha gustado de Common Lisp es que el sistema de condiciones es limpio, y que con los reinicios (restarts) se puede controlar fácilmente código que está muy lejos. El desarrollo guiado por REPL también resulta cómodo en situaciones donde no se sabe bien qué va a pasar, por ejemplo al parsear una fuente de datos desconocida, porque permite corregir el código y seguir sin perder el contexto.
Hay muchas implementaciones y la interoperabilidad también es buena; en un caso pude alternar entre SBCL para velocidad y CLISP para reducir el uso de memoria. Una de las razones por las que me incliné más por CL que por Scheme para aprender Lisp fue precisamente que hay varias implementaciones compatibles.
Aunque soy principiante en Emacs, la integración con Common Lisp fue excelente, y funciona bien incluso en la netbook lentísima que uso para desarrollar. En una época de computadoras rápidas, VS Code y servidores de lenguaje, ya no es una ventaja tan grande, pero definitivamente tiene una vibra retrofuturista.
También hay cosas que lamento. QuickLisp, el gestor de paquetes más popular, está bien, pero no llega al nivel de funcionalidades al que nos acostumbraron los ecosistemas de lenguajes modernos. El lenguaje en sí quedó congelado en el tiempo, así que para muchas cosas como hilos, sincronización y argumentos de línea de comandos se necesitan bibliotecas de interoperabilidad. Y sería realmente bueno que SBCL soportara compilaciones completamente estáticas para poder distribuir binarios también en distribuciones de Linux que no usan glibc.
Si no se admite cifrado ni autenticación para una tarea crítica como obtener código fuente, creo que QL difícilmente sea una opción para alguien que se preocupa por la seguridad.
Otro problema es que SBCL esté alojado en SourceForge. SourceForge en el pasado inyectó malware en archivos de descarga, así que también considero esto un problema de seguridad. Hoy no parece haber una razón válida para seguir usando SourceForge, y no entiendo bien por qué un proyecto importante todavía permanece ahí.
Me gustan mucho Lisp y, en especial, Common Lisp, pero cosas como estas me alejaron, y tampoco parece haber mucho interés en corregirlas.
https://www.timmons.dev/posts/static-executables-with-sbcl.h...
https://www.timmons.dev/posts/static-executables-with-sbcl-v...
https://cons.io
Gerbil/Gambit Scheme puede generar binarios completamente estáticos como alternativa a CL.
Se puede usar como biblioteca normal de Quicklisp, como imagen core de arranque instantáneo, o como binario. También puede ejecutar scripts casi al instante, así que en ese aspecto se parece a Babashka. Todavía estoy puliendo detalles y no es v1.0.
Lo de “manejar un error en tiempo de ejecución corrigiendo el código roto en el lugar, sin reiniciar” del blog debería haberse mostrado aquí: https://www.youtube.com/watch?v=jBBS4FeY7XM
Si estás corriendo un cálculo largo y pesado y aparece un error en el último paso, en lugar de volver a ejecutar todo desde el principio, entrás al depurador interactivo, vas a la línea con el error, compilás la función corregida, volvés al depurador y reanudás la ejecución desde un punto del stack frame. Después podés ver cómo el programa pasa.
M-es mucho mejor que lo que ofrece Clojure contap>. Incluso usando herramientas como Portal, me parece quetap>está más cerca de ser un pretty-print glorificado.En cuanto al rendimiento, Common Lisp también permite especificación de tipos estáticos, y SBCL puede generar código muy eficiente cuando se hace eso. Ayuda ejecutar
DISASSEMBLEsobre código propio para ver qué se emite realmente y optimizarlo. En particular, paquetes como SB-SIMD y Loopus ayudan muchísimo en aplicaciones de cálculo numérico.:quackes true, trátalo como pato”, encaja bien con el diseño de juegos de tipo entidad-componente-sistema.Pero durante el último año extrañé muchísimo la forma de desarrollar en Common Lisp y, por Dios, el sistema de condiciones. Ni siquiera soy un hacker tan hábil en CL, pero se me incorporó rápidamente. Ojalá CLOS y los tipos de datos primitivos de CL encajaran mejor de lo que parece.
En Cider definitivamente se puede hacer con facilidad, y supongo que las herramientas principales de otros editores también lo hacen suficientemente bien.
DISASSEMBLEhasta cierto punto.Hay proyectos auxiliares como https://github.com/Bronsa/tools.decompiler, y también está JitWatch de OpenJDK (https://github.com/AdoptOpenJDK/jitwatch). También hay herramientas similares para otras JVM
No es tan intuitivo como Lisp, pero aun así se puede
Está bien haber creado algo esotérico que funciona de manera atractiva, pero ahora nadie querrá tocarlo salvo personas cuyo sueldo se puede expresar en cantidad de ceros
https://www.digitalocean.com/community/tutorials/java-repl-j...
https://github.com/waf/CSharpRepl
https://pub.dev/packages/interactive
Sin mencionar Ruby, Python, PHP y Lua; incluso C++ tiene un REPL, aunque algo tosco: https://github.com/root-project/cling
Cada vez que digo que el REPL ya no es un factor diferenciador de Lisp, me llenan de downvotes
https://github.com/kaveh808/kons-9
Pero esas redefiniciones y sobrescrituras terminan contaminando el namespace, y en algún momento algo se rompe y hay que recargar el servidor
También tiene muchas desventajas, pero no son peores que las desventajas puramente técnicas de casi todos los demás lenguajes y entornos de desarrollo. Python y JS se usan más y tienen más bibliotecas, pero siento que la experiencia de desarrollo es mucho peor. Durante años he usado mucho C#, TS, Py, Hs y hasta lenguajes más esotéricos, pero cada vez que me atasco con ellos o me enojo seriamente por cosas claramente malas, termino volviendo a CL(SBCL + Emacs + Slime). Entonces me tranquilizo y recupero la certeza de que todavía hay cosas buenas en el mundo
Ahora estamos levantando inversión para un producto que tontamente bootstrappeamos en TypeScript, pero pensamos rehacer la versión de lanzamiento en CL. Eso significa que durante los próximos 3 a 5 años, antes del lanzamiento, voy a trabajar con CL y dentro de CL, y me alegra poder hacer cosas divertidas como implementar DSL, generar código, trabajar con macros e implementar un analizador de tipos estáticos
Cuando trabajas en C#, ¿qué extrañas de Lisp?
Mi idea radical es que la razón por la que la programación funcional no se volvió masiva es que, aunque claramente sirve para escribir programas, la mayor parte del software actual no es “un programa que se ejecuta desde la línea de comandos en mi PC/servidor y corre hasta terminar”, sino “un programa que arranca, responde a entradas del usuario y luego el usuario cierra”, o “un programa que arranca y luego responde a la red u otro I/O automático hasta que otro software le dice que se detenga”
Este tipo de tareas es mucho más difícil de hacer en un estilo funcional puro, o al menos así fue en las implementaciones funcionales con opiniones fuertes que probé. Porque ya no se trata simplemente de evaluar una expresión, sino de inicializar estado, reaccionar a I/O, actualizar estado o realizar I/O adicional, e incluso usar paralelización para monitoreo, recepción, I/O adicional y actualización de estado
Por supuesto, no es imposible hacer estas cosas con Lisp, pero por mi experiencia con algunos semestres de programación funcional en la universidad y usando profesionalmente características funcionales en C++/Scala para resolver estos problemas, me pareció bastante difícil hacer que la programación funcional funcionara bien en este tipo de aplicaciones, y esa falta de encaje me hace dudar a la hora de profundizar más en la programación funcional
Al margen de Common Lisp, con frecuencia tuve que escribir código que genera código. En este ámbito parece que Lisp podría brillar, pero todavía no tuve oportunidad de probarlo. Algunos ejemplos de proyectos recientes que hice antes de pensar en Common Lisp incluyen generar, en un generador de sitios estáticos, código para validar cierto JSON Schema, y generar JSX a partir de Markdown para usarlo en el contenido narrativo de un juego de programación
Por no hablar de la incontable cantidad de macros de C que he usado todo este tiempo
Tampoco hace falta tirar a la basura todas las buenas funciones de otros estilos. Parte de la programación funcional se está volviendo cada vez más común incluso en lenguajes generales. Por ejemplo, Rust también usa patrones funcionales en muchos casos
Y Lisp también se puede usar, si uno quiere, con estilo orientado a objetos o imperativo. No es Haskell
Veo a Lisp como el punto de partida y a Haskell como lo verdadero. Recomiendo aprender ese estilo, aunque no se escriba código real de producción
(let ((pair (cons 1 nil)))(setf (cdr pair) pair)(list (first pair) (second pair) (third pair)));; => (1 1 1)Si quieres algo que “compile para siempre”, escríbelo en ANSI C
Si quieres algo que “sobreviva para siempre”, escríbelo en Python 2.7 y conviértelo en la columna vertebral de las plantillas de infraestructura de toda la organización. Puntos extra si es un módulo Ansible personalizado
Lo que espero es que Lisp minimice o abstraiga lo accesorio y el boilerplate para que sea más fácil expresar ideas como código. Para mí, las expresiones Lisp parecen la forma más natural de expresar ideas
Me interesa sobre todo escribir apps con GUI, así que quiero aprender Clog o encontrar, o desarrollar, un buen wrapper sobre algún toolkit de GUI
(do-it (do-it first))(do-it first)funciona bien, pero ¿qué pasa si falla la llamada a(do-it (do-it first))?Sería bueno poder obtener el control en el punto roto y corregir la definición de
do-it. Pero si la corriges, también cambia el resultado de(do-it first)Entonces el punto en el que la máquina se encuentra actualmente pasa a ser un punto al que ya no se puede llegar con el código actual
Realmente no entiendo cómo funciona esto cuando haces una modificación que permitiría continuar, pero esa misma modificación cambia el estado en el que recibiste el control para hacerla