1 puntos por GN⁺ 2 시간 전 | 2 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2 시간 전
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

    • Julia Evans tiene un post divertido visualizando Gunzip con Julia: https://jvns.ca/blog/2013/10/24/day-16-gzip-plus-poetry-equa...
      Estaría bueno hacer también una versión de “Janet usa Janet”
    • Me pregunto si ya probaste jeep: https://github.com/pyrmont/jeep/
      Permite vendorizar dependencias e instalar fácilmente bundles modernos de Janet sin jpm
    • Si necesitas algo del lado del servidor, como dijo veqq, está joy, que implementa su propio servidor HTTP ligero. Si necesitas cliente, envolver libcurl u otro cliente HTTP con la API C de Janet es bastante sencillo
      Si estás abierto al desarrollo con LLM, puedes dejar que el LLM escriba el wrapper y escribir la lógica real en Janet
    • Me da curiosidad qué significa exactamente routing HTTP avanzado. Para todo lo web uso https://github.com/joy-framework/joy, así que si falta alguna función, probablemente podría agregarla en una semana
  • 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/

    • Fennel es realmente bueno y también una gran forma de entrar al mundo tipo Clojure. Mi queja principal es que el debugging es el típico campo minado de transpilers
      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 bloques match
      Creo 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

    • Es una función realmente genial, pero me pregunto en qué situaciones un programador promedio necesitaría este tipo de sandboxing
  • 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

    • En la sintaxis de Clojure, el uso de corchetes es muy consistente y bastante lógico
      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 ejecutables
      Los 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 especial
      La ú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
    • También puedes simplemente no usar corchetes ni llaves. En lugar de [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
    • Si entiendes cómo funciona la desestructuración en Clojure, queda claro el papel que cumplen los corchetes
  • 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.

    • En mi caso, babashka reemplazó sh, Python, awk, etc.
  • 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.

    • Por si no has seguido la tendencia reciente, el relanzamiento más reciente de digg.com fracasó porque no pudo soportar el asalto de bots.
      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...
    • Pienso seguido en cómo podría construirse una comunidad en línea donde no se permita la IA. En especial, hacerlo sin destruir el anonimato en línea es lo difícil.
      Algún tipo de “prueba de humanidad” es un problema complicado.
    • Lo increíble de la IA es que ni siquiera hace falta ser un entusiasta de la IA para meterla en conversaciones que no tienen nada que ver con IA. Los detractores lo hacen por ti.
    • Ese lugar probablemente sería lobsters. Después de una discusión de más de 300 comentarios, quizá la más grande en la historia del sitio, se prohibieron los textos de IA.
      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.
    • Irónicamente, este comentario, que es el comentario principal, ahora también trata sobre IA.
  • 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.

    • Un ejemplo de un macro de C con literales que no tiene transparencia referencial sería este:
      #define MULTIPLY(x, y) x * y
      int result = MULTIPLY(2 + 3, 4); // 14
      Que 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.
    • Me pregunto si alguna vez has intentado explicarle a una persona cualquiera en la calle qué es un lenguaje de programación orientado a objetos y cuáles son sus ventajas.
    • Hace como un año empecé a escribir un intérprete de Scheme y avancé bastante. Lo dejé hace unos meses cuando conseguí un nuevo trabajo.
      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 quitar call/cc, y concentrarme en depurabilidad, ecosistema, rendimiento y gestión de paquetes.
    • Casi siempre recibo una reacción así cuando me preguntan “¿A qué te dedicas?”.
      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.
    • Probablemente al programador promedio también le pase eso.
      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.

    • También hay material de introducción más amigable: https://janetdocs.org/tutorials
    • Qué raro. Este lenguaje es muy intuitivo y simple, y tiene poquísimas reglas que recordar. Es Lisp, sí, pero su superficie es muy pequeña.
      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.
    • En lo personal, me atoro porque la sintaxis de macros aparece demasiado temprano, pero después de eso sí hay muchísimo contenido valioso.
    • Me pasó algo parecido con Haskell. Haskell es demasiado difícil para mí, pero su sintaxis en sí me gusta.
      Janet se ve como un Lisp 2.0, así que la sintaxis también es de estilo Lisp.
 
GN⁺ 2 시간 전
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 eso
    Con 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 .tsv dentro 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 usando embed
    Si 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úa

    • Un ejemplo genial hecho con Janet es https://bauble.studio, y también está el artículo sobre cómo se hizo en https://ianthehenry.com/posts/bauble/building-bauble/. Destaca bastante el uso de WASM
      Tambié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 real
    • Creo que en el texto original, decir “colecciones inmutables” fue una forma imprecisa de expresarlo. Lo que se quería decir estaba más cerca de tipos de valor compuestos y tipos por referencia
      Cuando 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

    • Yo diría que una de las funciones estrella de Janet es su soporte integrado para PEG. Me resultó realmente práctico al crear un DSL de textbox para un proyecto paralelo de desarrollo de juegos
      Es una de esas funciones cuya falta se nota cada vez que parseas texto en otros lenguajes
    • Ese tipo de desestructuración no es una forma de uso frecuente, y los arrays/tuplas de Janet no son listas enlazadas
      Más que un Clojure pequeño y embebible, Janet se parece más a un Lua con mejor soporte para programación funcional