- Janet es un dialecto pequeño de Lisp, pero permite empezar de forma simple gracias a que tiene lenguaje imperativo, funciones de primera clase, un espacio de nombres único para identificadores y alcance léxico por bloques
- El lenguaje núcleo está compuesto solo por 8 formas:
do, def, var, set, if, while, break, fn, y los macros crean envolturas de control de flujo más potentes o convenientes
- La capacidad de despliegue se logra compilando programas de Janet como ejecutables nativos enlazados estáticamente con el runtime de Janet, sin exigir al usuario instalar Janet ni dependencias
- La gramática de expresiones de análisis (PEG) es más simple, potente y predecible que las expresiones regulares, y sh permite expresar pipes y redirecciones dentro de Janet, ampliando el alcance para escribir CLI
- La ejecución en tiempo de compilación ejecuta primero las órdenes de nivel superior y luego guarda en disco una instantánea del estado del programa, lo que permite pasar al runtime valores, referencias compartidas, generadores y estado de cierres incluso sin macros
Un núcleo simple
- Janet es un lenguaje imperativo con funciones de primera clase, un espacio de nombres único para identificadores y alcance léxico por bloques
- El núcleo del lenguaje se mantiene pequeño con 8 formas:
do, def, var, set, if, while, break, fn
- Los macros permiten envolturas de control de flujo de alto nivel más potentes o convenientes
- La semántica de runtime es familiar, y el resto del lenguaje también es pequeño: toda la biblioteca estándar cabe en una sola página
Despliegue nativo e integración embebida
- Los programas de Janet pueden compilarse fácilmente como ejecutables nativos enlazados estáticamente con el runtime de Janet
- Los usuarios que los reciben no necesitan instalar Janet, dependencias del proyecto ni ningún otro componente aparte
- Janet se compila a sí mismo a bytecode, luego escribe ese bytecode dentro de un archivo
.c que inicializa el runtime de Janet y compila ese archivo C con el compilador de C del sistema
- Un binario nativo simple de “hello world” ocupa menos de 1 MB; en Janet 1.27.0, en aarch64 macOS, medía 784K
- Ese binario incluye todo el runtime de Janet, el recolector de basura e incluso el compilador de bytecode, por lo que también se pueden crear programas que evalúen código Janet en runtime
- El runtime de Janet es una biblioteca pequeña en C, así que después de enlazarla se pueden manipular valores de Janet llamando funciones normales de C
- También puede integrarse en sitios web, y permite crear sitios estáticos con DSL programables definidos por el usuario, como Toodle
Parsing de texto y DSL de subprocesos
- El procesamiento de texto en Janet se basa en gramáticas de expresiones de análisis en lugar de expresiones regulares
- Las gramáticas de expresiones de análisis son más simples, potentes y predecibles que las expresiones regulares, y al no estar atadas a una sola línea pueden parsear texto multilínea
- Pueden parsear HTML, JSON y otros lenguajes no regulares, y también manejar formatos binarios con bytes nulos arbitrarios
- sh es un DSL de scripting de shell de terceros que permite expresar pipes y redirecciones directamente dentro del código Janet
($ find . -name *.janet | say)
- Este DSL eleva a Janet desde una alternativa razonable a Perl hasta una alternativa razonable a Bash para un rango bastante amplio de programas
Colecciones y sensación de la sintaxis
- Los tipos de colección de Janet tienen formas mutables e inmutables
- Las colecciones inmutables tienen semántica de valor, así que el vector inmutable
[1 2] no se distingue de (take 2 [1 2 3]) aunque tengan direcciones de memoria distintas
- Las colecciones mutables tienen semántica de referencia, así que la tabla hash
@{:x 1 :y 2} solo es igual a sí misma, y otra tabla hash con las mismas claves y valores es un objeto distinto
- La sintaxis usa ampliamente paréntesis, pero distingue las formas usando
[] para listas y {} para tablas
- Los literales mutables siempre llevan el prefijo
@, como en @"mutable string"
- Las funciones anónimas se escriben como
(fn [x] (+ 1 x)), y también ofrece una forma abreviada para elevar una expresión a función con |, como |(+ 1 $)
- El splat o spread se expresa con
;, como en (+ ;args)
- Las cadenas con backticks pueden abrirse con la cantidad de backticks que se quiera y cerrarse con la misma cantidad, y dentro de ellas no se aplican secuencias de escape como
\n
- Los parámetros restantes se escriben con
& en lugar de ., como en (defn foo [first & rest] ...)
- Janet no soporta reader macros, así que la sintaxis misma queda fija, y si sabes leer Janet puedes leer cualquier programa Janet
Macros y estado en tiempo de compilación
- Los macros de Janet son código que escribe código, y en tiempo de compilación manejan a la vez el flujo de ejecución actual que manipula valores y árboles de sintaxis abstracta, y el flujo del código de la aplicación que se ejecutará después
- Los macros de Janet no son higiénicos (
hygienic), y tampoco existe un espacio de nombres separado para funciones
- Pero se pueden hacer unquote de funciones literales, lo que permite escribir macros completamente transparentes referencialmente
- Cuando se compila un programa Janet, primero se ejecutan las órdenes de nivel superior, sentencias comunes y declaraciones de funciones, y luego se escribe en disco una instantánea del estado del programa
- Esa instantánea preserva las referencias compartidas, así que tras reiniciar se puede seguir mutando el mismo valor
- Los generadores recuerdan la siguiente instrucción que deben reanudar, y los cierres también conservan los valores capturados
- Los macros son una forma especial de ejecución de código en tiempo de compilación, pero esta capacidad puede usarse incluso sin macros
- En juegos, se pueden preprocesar splines; también se pueden leer archivos en tiempo de compilación para incluir assets en el binario final, y ejecutar efectos secundarios arbitrarios
- Janet for Mortals muestra un ejemplo que genera automáticamente bindings de base de datos a partir de un archivo de esquema SQL, algo que considera bastante difícil en la mayoría de los lenguajes
Más cómodo que la tradición Lisp
- Janet no sigue al pie de la letra las convenciones antiguas de Lisp
CAR se llama first, PROGN se llama do, LAMBDA se llama fn y SETQ se llama def
nil no es una lista vacía sino un tipo independiente, y los booleanos son valores de primera clase
- Evita la familia
EQ, EQL, EQUAL, EQUALP, y las listas enlazadas casi no aparecen
2 comentarios
Comentarios de Hacker News
Janet tiene algunos puntos flojos. Principalmente, le falta versionado en la gestión de paquetes y en general faltan bibliotecas para cosas como routing HTTP avanzado
Aun así, me encanta que con JPM se puedan crear binarios y scripts, y que tenga buena portabilidad. Incluso una vez probé, como prueba de concepto, portar el lenguaje de programación Janet a la consola de juegos Playdate
Disfruto escribir código en Janet, pero cada vez que lo hago es un poco incómodo que la gente crea que yo hice este lenguaje
Estaría bueno hacer también una versión de “Janet usa Janet”
Permite vendorizar dependencias e instalar fácilmente bundles modernos de Janet sin jpm
Si estás abierto al desarrollo con LLM, puedes dejar que el LLM escriba el wrapper y escribir la lógica real en Janet
También existe Fennel, un lenguaje parecido que el mismo desarrollador hizo antes. Compila a Lua y toda la implementación también está hecha en Lua
No tiene biblioteca estándar propia, así que le faltan muchas cosas buenas como la biblioteca de parsing de Janet, pero sirve bien para escribir scripts en entornos que integran Lua
https://fennel-lang.org/
La conexión entre Fennel y la VM de Lua es muy frágil, y no llega ni a la mitad de la calidad del debugger y el REPL de Janet. Es una lástima, porque Fennel es mucho más portable y gracias a LuaJIT incluso puede superar ampliamente a SBCL
Pero siento que la experiencia de transpilar lo lastra por completo. Hay formas de rodearlo, pero incluso si implementas
debug.setinfo, te topas con casos límite menos agradables como bloquesmatchCreo que habría mucho valor en hacer un fork de LuaJIT2 para corregir la estructura de debugging y errores de forma que sea más transparente para el lenguaje. Entonces lenguajes como Fennel se verían mucho más atractivos
El autor hizo estas herramientas con Janet, y ya se habían comentado antes en HN
https://bauble.studio
https://toodle.studio
Estas dos interesantes herramientas artísticas hicieron que durante un tiempo tuviera bastante expectativa con Janet
Siempre da gusto ver que Janet recibe atención. Como una de sus funciones modernas, me gustaría destacar el sandbox
“Desactiva conjuntos de capacidades para que el intérprete no pueda usar ciertos recursos del sistema. Una vez desactivada una capacidad, no hay forma de volver a activarla.”
https://janet-lang.org/api/misc.html#sandbox
Cuando vi “SETQ is def”, al principio dije en voz alta “¿qué?”. Porque SETQ no crea bindings, solo los actualiza
Al leer la documentación (https://janet-lang.org/docs/bindings.html), parece que el autor en realidad estaba equivocado, y dice que “los bindings creados con def son inmutables”. Supongo que quiso decir “SETQ is set”
Janet me parece un punto intermedio muy adecuado entre Guile, Tcl y CL, y de verdad me gustaría que me encantara, pero tengo un rechazo visceral a usar corchetes vectoriales para lambdas y operadores de control de flujo. Me pasa lo mismo con Clojure y me cuesta muchísimo superarlo, aunque quizá con suficiente esfuerzo podría hacerlo
Y también me pregunto en qué estado están hoy LSP/SLIME. A estas alturas, eso importa bastante
Si usas paréntesis, el primer elemento de la lista determina cómo se interpretará el resto. Por ejemplo,
(func a b c)es una ejecución de función,(macro x y z)es una expansión de macro, y([p q r] …)es un cuerpo de función “desnudo” que empieza con un vector de parámetros y sigue con expresiones ejecutablesLos corchetes se usan cuando los elementos son del mismo “tipo” y el primero no es especial. Por ejemplo,
(defn f [a b c] …)es una colección de parámetros del mismo tipo y el primer parámetro no tiene nada especial, y(let [a 1 b 2] …)también es una colección de bindings donde el primero no es especialLa única excepción que se me ocurre es en
case, cuando se agrupan varios elementos de matching, pero eso es por conveniencia. Cuando entendí esta lógica cambié de opinión, y desde entonces me parece hermoso[1 2 3], puedes escribir(array 1 2 3), y en lugar de(fn [x] (+ 1 x)), puedes escribir(f (x) (+ 1 x))No son obligatorios
En scripts de sistema que superan cierta longitud, Janet me ha reemplazado sh, Python, awk, etc.
El tiempo de arranque al ejecutar scripts es muy rápido, y en mi sistema marca 1.4 ms según hyperfine, parecido a 1 ms de dash. Esto es para scripts, no para ejecutables compilados.
Gracias al módulo
sh-dsl, se pueden escribir comandos de shell de forma muy elegante, como($ cmda w x | cmdb y z). La función de poder cargar una imagen para depurar también ayuda muchísimo.Empecé a usarlo hace muy poco, pero ya parece que será uno de mis lenguajes favoritos, y el único otro Lisp que había usado antes era MIT Scheme para SICP.
Este post se siente fresco. Tiene ese olor a debate de antes de la IA en internet.
Hay un lenguaje nuevo, una sintaxis nueva y discusiones intensas entre gente que lleva años escribiendo código. Ojalá alguien cree una comunidad en línea donde no se permita la IA.
En realidad, supongo que habría que decir el relanzamiento anterior, y ahora parece que otra vez tienen una nueva página principal. Quien primero descubra cómo bloquear de forma confiable a la IA en comunidades en línea probablemente se hará muy rico.
https://www.techspot.com/news/111698-digg-relaunch-fails-two...
Algún tipo de “prueba de humanidad” es un problema complicado.
La regla exacta fijada por la administración era “autoría humana significativa”, pero no se engañen. En lobsters hay mucha gente ideológicamente opuesta a los LLM. No importa mucho qué tan “significativamente” se haya aplicado la tecnología.
Mi trabajo fue clasificado como basura solo porque la IA lo había tocado, y hubo quienes me llamaron exhibicionista o fetichista cuando dije que usaba IA. Solo quiero advertirlo de antemano a quien esté pensando en registrarse.
Con frases como “al permitir desquote de funciones literales, Janet hace posible escribir macros completamente referencialmente transparentes”, parece que la gente de Lisp de verdad se emociona con cosas muy abstractas.
Si le dijeras eso a una persona cualquiera en la calle, probablemente saldría corriendo.
#define MULTIPLY(x, y) x * yint result = MULTIPLY(2 + 3, 4); // 14Que no sepas el significado de algo no quiere decir que sea malo. Por cómo está redactado, supongo que iba por ahí.
Tener un lenguaje compartido para patrones y problemas que aparecen repetidamente en programación es algo bueno. Burlarse de la terminología con actitud de “ese grupo de programadores sí que es raro” no sirve de nada y hasta resulta contraproducente.
Estoy pensando en retomarlo, pero me pregunto si vale la pena implementar funciones de nicho. A mí también me cuesta implementarlas. Tal vez sería mejor saltarme
dynamic-unwind, quizá incluso quitarcall/cc, y concentrarme en depurabilidad, ecosistema, rendimiento y gestión de paquetes.Por eso intento decir algo muy vago, como “trabajo en algo relacionado con computadoras”, o digo “no es algo muy interesante” y cambio de tema. Si doy un poco más de detalle, la gente empieza a buscar la salida.
Sinceramente, creo que las comunidades de la familia Lisp incluso se beneficiaron de ser pequeñas. Por ejemplo, hasta el antiquísimo Design Patterns advertía que se prefiriera composición sobre herencia, y aun así los programadores orientados a objetos seguían construyendo jerarquías de 15 niveles de profundidad.
Cuando conocí Janet por primera vez, estos documentos me ayudaron muchísimo.
https://janetdocs.org/tutorials
https://janet.guide/ hecho por el autor del post.
A veces me llamaban la atención los posts sobre Janet que aparecen en HN, pero Janet for Mortals, que todos valoran tanto, no me pareció en absoluto un libro para mortales.
Comparado con otros lenguajes, Janet realmente está entre los más fáciles de aprender, así que sorprende que ese libro sea difícil. No he leído el libro, pero sí conozco bastante el lenguaje y, honestamente, no tengo más que elogios.
Janet se ve como un Lisp 2.0, así que la sintaxis también es de estilo Lisp.
Opiniones en Lobste.rs
Tras 10 meses usando Janet, me enganchó tanto que casi olvidé todos los lenguajes de la familia APL, y mientras mantengo el sitio de documentación de la comunidad también estoy escribiendo tutoriales
En las primeras 3 semanas reescribí todos mis scripts personales, y el nuevo software operativo que voy creando también lo escribo en Janet
En su implementación, Janet hace que casi todo el lenguaje funcione como un hash map, así que puedes ver los símbolos locales con
(keys (curenv))y los símbolos del core con(keys (getproto (curenv))); si quieres, incluso puedes construir algo parecido a CLOS sobre hash maps, y hay una implementación de esoCon el framework web Joy estoy corriendo unas 20 páginas web y varios servicios en un solo VPS gratuito de 512MB, y también escribí un tutorial relacionado
Aun así, la expresión “colecciones inmutables” es algo distinta de la realidad; la biblioteca estándar por lo general devuelve valores mutables, así que hoy por hoy no hay una gran razón para insistir en la inmutabilidad
La capacidad de pasar valores de tiempo de compilación al tiempo de ejecución me pareció especialmente poderosa. Por ejemplo, si metes una Biblia en
.tsvdentro del binario como hash map en compilación, en ejecución solo haces consultas, y eso dio un resultado dos veces más rápido que la versión en Go usandoembedSi implementaras lo mismo a mano en Go como hash map, quedaría mucho más largo, pero en Janet incluso pude hacer un compilador de Lisp a Go en 46 líneas
La parte más interesante que mencionó Ian Henry es que Janet preserva el estado de los closures entre imágenes/sesiones: si guardas el entorno relacionado en el hash map de
(curenv)y luego lo restauras en una nueva sesión de REPL, el estado interno del closure continúaTambién está https://lisp.trane.studio/, un DSL musical basado en Lisp, y vale la pena ver el paper https://dl.acm.org/doi/abs/10.1145/3677996.3678285 y un ejemplo de resultados en https://x.com/greg_ash/status/1824218993118388708
También tengo una biblioteca propia que ofrece una sintaxis de consultas parecida a SQL sobre varias estructuras de datos
Soporta inserción y actualización de dataframes, así como guardado/carga de CSV, e incluye Datalog y miniKanren; también permite operaciones vectorizadas al estilo APL
También existe jnj, que usa J directamente desde Janet, y Joy Web Framework tiene un DSL de consultas a BD como
(var account (db/find-by :account :where {:login (auth-result :login)})), que también se usa en el código de autenticación de un sitio realCuando uno lee “colecciones inmutables”, piensa en estructuras de datos persistentes, y aunque eso puede ser útil, no es una capacidad base de Janet
Lo que realmente me gusta es la simetría entre tipos de valor y tipos por referencia, y aunque al final del texto sí decía “immutable composite values”, si no lo hubiera escrito yo mismo probablemente tampoco habría captado enseguida a qué se refería
Janet se siente fresco y es un lenguaje embebible, así que me gustaría probarlo como scripting integrado en proyectos como motores de juego
Parece encajar bien en lugares donde necesitas hot reload para iterar rápido sin reemplazar DLLs; Lua también es excelente, pero Janet parece más expresivo en algunos aspectos
Janet como lenguaje es realmente genial, y algún día me gustaría usarlo como lenguaje de scripting en un proyecto con Zig. Da gusto ver que más gente mencione Janet
Se ve bien, pero ya me estoy acostumbrando a hacer scripting en Clojure con babashka, así que me da una sensación parecida. Me pregunto si, aparte de la capacidad de ser embebido, hay alguna gran ventaja que se me esté escapando
Cosas como “la desestructuración de arrays con argumentos rest puede provocar copias potencialmente costosas” hacen que en general parezca menos funcional, y eso no me convence
Es una de esas funciones cuya falta se nota cada vez que parseas texto en otros lenguajes
Más que un Clojure pequeño y embebible, Janet se parece más a un Lua con mejor soporte para programación funcional