- Janet est un petit dialecte de Lisp, mais il permet de démarrer simplement grâce à son caractère impératif, ses fonctions de première classe, son espace de noms à identifiant unique et sa portée lexicale par bloc
- Le langage cœur se compose de seulement 8 commandes :
do, def, var, set, if, while, break, fn, et les macros permettent de créer des wrappers de contrôle de flux plus puissants ou plus pratiques
- La déployabilité est assurée en compilant un programme Janet en exécutable natif lié statiquement au runtime Janet, sans exiger des utilisateurs qu’ils installent Janet ou des dépendances
- Les grammaires d’expression d’analyse (PEG) sont plus simples, plus puissantes et plus prévisibles que les expressions régulières, et sh permet d’exprimer pipes et redirections directement dans Janet, ce qui élargit le champ d’écriture d’outils CLI
- L’exécution au moment de la compilation exécute d’abord les commandes top-level puis enregistre un instantané de l’état du programme sur disque, ce qui permet de transmettre au runtime des valeurs, références partagées, générateurs et états de fermeture, même sans macros
Un cœur simple
- Janet est un langage impératif avec des fonctions de première classe, un espace de noms à identifiant unique et une portée lexicale par bloc
- Le cœur du langage reste réduit à 8 commandes :
do, def, var, set, if, while, break, fn
- Les macros rendent possibles des wrappers de contrôle de flux de plus haut niveau, plus puissants ou plus pratiques
- La sémantique d’exécution est familière, et le reste du langage est lui aussi compact : toute la bibliothèque standard tient sur une seule page
Déploiement natif et embarqué
- Les programmes Janet peuvent facilement être compilés en exécutables natifs liés statiquement au runtime Janet
- Les utilisateurs qui reçoivent le binaire n’ont pas besoin d’installer Janet, les dépendances du projet ou d’autres composants séparés
- Janet se compile lui-même en bytecode, écrit ensuite ce bytecode dans un fichier
.c qui initialise le runtime Janet, puis compile ce fichier C avec le compilateur C du système
- Un simple binaire natif « hello world » fait moins de 1 Mo, et avec Janet 1.27.0 sur macOS aarch64, il pesait 784 K
- Ce binaire contient même l’intégralité du runtime Janet, le garbage collector et le compilateur de bytecode, ce qui permet de créer des programmes capables d’évaluer du code Janet à l’exécution
- Le runtime Janet est une petite bibliothèque C : une fois liée, on peut manipuler des valeurs Janet en appelant des fonctions C ordinaires
- Il peut aussi être embarqué dans des sites web et servir à créer des sites statiques avec une DSL programmable personnalisée, comme Toodle
Parsing de texte et DSL de sous-processus
- Le traitement de texte de Janet repose sur des grammaires d’expression d’analyse plutôt que sur des expressions régulières
- Les grammaires d’expression d’analyse sont plus simples, plus puissantes et plus prévisibles que les expressions régulières, et ne sont pas limitées à une ligne, ce qui permet de parser du texte sur plusieurs lignes
- Elles permettent de parser du HTML, du JSON et d’autres langages non réguliers, ainsi que des formats de fichiers binaires contenant des octets nuls arbitraires
- sh est une DSL tierce de scripting shell qui permet d’exprimer directement pipes et redirections dans du code Janet
($ find . -name *.janet | say)
- Cette DSL fait passer Janet d’une alternative raisonnable à Perl à une alternative raisonnable à Bash pour une gamme assez large de programmes
Collections et sensation syntaxique
- Les types de collection de Janet existent à la fois en version mutable et immutable
- Les collections immutables ont une sémantique de valeur : ainsi, le vecteur immutable
[1 2] ne se distingue pas de (take 2 [1 2 3]), même si leurs adresses mémoire diffèrent
- Les collections mutables ont une sémantique de référence : ainsi, la table de hachage
@{:x 1 :y 2} n’est égale qu’à elle-même, et une autre table de hachage avec les mêmes clés et valeurs reste un objet distinct
- La syntaxe utilise largement les parenthèses, mais distingue les formes avec
[] pour les listes et {} pour les tables
- Les littéraux mutables portent toujours le préfixe
@, comme dans @"mutable string"
- Une fonction anonyme s’écrit
(fn [x] (+ 1 x)), et il existe aussi une notation abrégée qui élève une expression en fonction avec |, comme |(+ 1 $)
- Le splat ou spread s’exprime avec
;, comme dans (+ ;args)
- Les chaînes entre backticks peuvent s’ouvrir avec autant de backticks que souhaité et se fermer avec le même nombre, et à l’intérieur, les séquences d’échappement comme
\n ne s’appliquent pas
- Les paramètres restants utilisent
& au lieu de ., comme dans (defn foo [first & rest] ...)
- Janet ne prend pas en charge les reader macros, donc la syntaxe elle-même est fixe : si l’on sait lire Janet, on peut lire n’importe quel programme Janet
Macros et état au moment de la compilation
- Les macros Janet sont du code qui écrit du code ; au moment de la compilation, elles manipulent à la fois des valeurs et des arbres syntaxiques abstraits, en traitant simultanément le flux d’exécution courant et le flux futur du code applicatif qui sera exécuté plus tard
- Les macros Janet ne sont pas hygiéniques et il n’existe pas d’espace de noms séparé pour les fonctions
- Mais comme on peut unquote des fonctions littérales, il est possible d’écrire des macros totalement transparentes référentiellement
- Quand on compile un programme Janet, les commandes top-level, les instructions ordinaires et les déclarations de fonctions sont d’abord exécutées, puis un instantané de l’état du programme est écrit sur disque
- Cet instantané préserve les références partagées, ce qui permet de continuer à modifier des valeurs mutables après redémarrage
- Les générateurs se souviennent de la prochaine instruction à reprendre, et les fermetures conservent elles aussi leurs valeurs capturées
- Les macros sont une forme particulière d’exécution de code au moment de la compilation, mais cette capacité peut aussi être utilisée sans macros
- Dans un jeu, on peut prétraiter des splines, lire des fichiers à la compilation pour intégrer des assets dans le binaire final, et effectuer des effets de bord arbitraires
- Janet for Mortals montre un exemple de génération automatique de bindings de base de données à partir d’un fichier de schéma SQL, une tâche jugée assez difficile dans la plupart des langages
Plus confortable que la tradition Lisp
- Janet ne reprend pas tel quel les anciennes conventions de Lisp
CAR s’appelle first, PROGN devient do, LAMBDA devient fn et SETQ devient def
nil n’est pas une liste vide mais un type distinct, et les booléens sont des valeurs de première classe
- Janet évite la famille
EQ, EQL, EQUAL, EQUALP, et les listes chaînées y sont presque absentes
2 commentaires
Avis sur Hacker News
Janet a quelques points faibles. Principalement le manque de versionnement précis pour la gestion des paquets, et plus généralement un manque de bibliothèques, par exemple pour le routage HTTP avancé
Cela dit, j’aime vraiment le fait qu’on puisse créer des binaires et des scripts avec JPM, ainsi que sa bonne portabilité. J’ai même déjà porté le langage Janet sur la console de jeu Playdate comme preuve de concept
J’aime écrire du code en Janet, mais c’est un peu gênant que les gens pensent à chaque fois que c’est moi qui ai créé ce langage
Ce serait sympa d’avoir aussi une version « Janet écrit Janet »
Il permet de vendoriser les dépendances et d’installer facilement des bundles Janet modernes sans jpm
Si tu es ouvert au développement avec des LLM, tu peux faire écrire les wrappers par un LLM et garder la vraie logique en Janet
Il existe aussi Fennel, un langage similaire créé plus tôt par le même développeur. Il compile vers Lua et son implémentation est elle aussi entièrement en Lua
Il n’a pas de bibliothèque standard propre, donc il lui manque beaucoup de bonnes choses qu’on trouve dans Janet, comme les bibliothèques de parsing, mais il est bien adapté pour écrire des scripts dans des environnements qui embarquent Lua
https://fennel-lang.org/
La jonction entre Fennel et la VM Lua est très fragile, et on est loin d’atteindre ne serait-ce que la moitié de la qualité du débogueur et du REPL de Janet. C’est d’autant plus frustrant que Fennel est bien plus portable et qu’avec LuaJIT il peut surpasser SBCL
Mais à mon avis, l’expérience de transpilation plombe complètement l’ensemble. Il existe des contournements, mais même si on implémente
debug.setinfo, on finit par tomber sur des cas limites peu agréables comme les blocsmatchJe pense qu’il y aurait énormément de valeur à forker LuaJIT2 pour corriger la structure du débogage et des erreurs afin qu’elle soit plus transparente du point de vue du langage. Des langages comme Fennel deviendraient alors beaucoup plus séduisants
L’auteur de l’article a déjà créé ces outils en Janet, qui avaient aussi été mentionnés sur HN auparavant
https://bauble.studio
https://toodle.studio
Ces deux outils artistiques intéressants m’ont rendu assez optimiste sur Janet pendant un moment
Je suis toujours content de voir Janet attirer de l’attention. Parmi ses fonctionnalités modernes, je citerais le sandbox
« désactiver des ensembles de fonctionnalités pour empêcher l’interpréteur d’utiliser certaines ressources système. Une fois une fonctionnalité désactivée, il n’existe aucun moyen de la réactiver. »
https://janet-lang.org/api/misc.html#sandbox
En lisant « SETQ is def », ma première réaction a été de dire à voix haute : « quoi ? » En effet, SETQ ne crée pas de binding, il ne fait que le mettre à jour
En lisant la documentation (https://janet-lang.org/docs/bindings.html), il semble que l’auteur s’est effectivement trompé, puisqu’il est indiqué que « les bindings créés avec def sont immuables ». Il voulait sans doute dire « SETQ is set »
J’ai vraiment envie d’aimer Janet, qui semble être un bon point d’équilibre entre Guile, Tcl et CL, mais j’ai un rejet instinctif de l’usage des vecteurs entre crochets pour les lambdas et les opérateurs de contrôle de flux. J’ai le même problème avec Clojure, que j’ai toujours du mal à accepter, même si avec suffisamment d’efforts ça finirait peut-être par passer
Et je me demande aussi dans quel état sont actuellement LSP/SLIME. De nos jours, c’est assez important
Avec des parenthèses, le premier élément d’une liste détermine comment le reste de la liste doit être interprété. Par exemple,
(func a b c)exécute une fonction,(macro x y z)déclenche une expansion de macro, et([p q r] …)est un corps de fonction « nu » qui commence par un vecteur de paramètres et se poursuit avec des expressions à exécuterOn utilise des crochets quand les éléments sont du même « type » et que le premier n’a pas de rôle spécial. Par exemple,
(defn f [a b c] …)représente un ensemble de paramètres de même nature où le premier paramètre n’a rien de particulier, et(let [a 1 b 2] …)représente aussi un ensemble de bindings où le premier binding n’a pas de statut spécialLa seule exception qui me vient à l’esprit est le cas de
case, où l’on regroupe plusieurs éléments à comparer, mais c’est pour des raisons de commodité. Une fois cette logique comprise, j’ai changé d’avis et j’ai fini par trouver ça beau[1 2 3], on peut écrire(array 1 2 3), et au lieu de(fn [x] (+ 1 x)), on peut écrire(f (x) (+ 1 x))Ce n’est pas obligatoire
Dans les scripts système d’une certaine longueur, Janet remplace pour moi sh, Python, awk, etc.
Le temps de démarrage d’exécution des scripts est très rapide, et sur mon système c’est 1,4 ms selon hyperfine, soit à peu près comme dash à 1 ms. Je parle bien de scripts, pas d’exécutables compilés.
Grâce au module
sh-dsl, on peut écrire des commandes shell de façon très élégante, comme($ cmda w x | cmdb y z). La possibilité de charger une image pour le débogage aide aussi énormément.Je n’ai commencé à l’utiliser que très récemment, mais j’ai déjà l’impression qu’il va devenir l’un de mes langages préférés, et le seul autre Lisp que j’avais utilisé auparavant était MIT Scheme pour SICP.
Ce fil est rafraîchissant. Il a une odeur de discussion d’avant l’IA sur Internet.
Il y a un nouveau langage, une nouvelle syntaxe, et des débats passionnés entre des gens qui écrivent du code depuis des années. J’aimerais que quelqu’un lance une communauté en ligne où l’IA n’est pas autorisée.
En fait, il faudrait sans doute parler de l’avant-dernière relance, et il semble qu’il y ait maintenant encore une nouvelle page d’accueil. La première personne qui trouvera un moyen fiable d’empêcher l’IA d’envahir les communautés en ligne a de fortes chances de devenir très riche.
https://www.techspot.com/news/111698-digg-relaunch-fails-two...
Une forme de « preuve d’humanité » est un problème difficile à résoudre.
La règle exacte fixée par l’équipe était « une paternité humaine significative », mais il ne faut pas se laisser tromper. Sur lobsters, beaucoup de gens sont idéologiquement opposés aux LLM. L’importance d’un usage « significatif » de la technologie compte peu.
Mon travail a été classé comme un déchet simplement parce que l’IA y avait touché, et certaines personnes m’ont traité d’exhibitionniste ou de fétichiste quand j’ai dit que j’utilisais l’IA. Je préfère prévenir à l’avance ceux qui envisagent de s’y inscrire.
À voir des phrases comme « en permettant de déquoter des fonctions littérales, Janet permet d’écrire des macros totalement transparentes par référence », on dirait que les gens du monde Lisp s’enthousiasment vraiment pour des choses très abstraites.
Si vous dites ça à quelqu’un dans la rue, il essaiera probablement de s’enfuir.
#define MULTIPLY(x, y) x * yint result = MULTIPLY(2 + 3, 4); // 14Ce n’est pas parce qu’on ne connaît pas le sens d’un terme qu’il est mauvais. Vu la formulation, c’est probablement dans ce sens que c’était dit.
Avoir un langage partagé pour parler de motifs et de problèmes récurrents en programmation est une bonne chose. Se moquer de la terminologie en disant « ce groupe de programmeurs est vraiment bizarre » est inutile et contre-productif.
Je me demande si je devrais m’y remettre, mais j’hésite sur l’intérêt d’implémenter des fonctionnalités de niche. Pour moi, ce sont aussi les plus difficiles à implémenter. Je ferais peut-être mieux d’ignorer
dynamic-unwind, voire de supprimercall/cc, puis de me concentrer sur la débogabilité, l’écosystème, les performances et la gestion des paquets.Du coup, je reste très vague en disant quelque chose comme « je travaille dans l’informatique », ou bien « ce n’est pas très intéressant », puis j’essaie de changer de sujet. Dès que j’entre un peu plus dans le détail, les gens commencent à chercher la sortie.
Honnêtement, je pense que les communautés de la famille Lisp y ont plutôt gagné parce qu’elles sont restées petites. Par exemple, même le très ancien Design Patterns mettait déjà en garde en faveur de la composition plutôt que de l’héritage, et pourtant les programmeurs orientés objet continuaient à construire des hiérarchies profondes de 15 niveaux.
Quand j’ai découvert Janet, ces documents m’ont vraiment aidé :
https://janetdocs.org/tutorials
https://janet.guide/ créé par l’auteur du billet
J’ai souvent été attiré par les billets sur Janet qui apparaissent parfois sur HN, mais Janet for Mortals, que tout le monde semble encenser, ne m’a pas du tout donné l’impression d’être un livre pour mortels.
Comparé à d’autres langages, Janet est vraiment plutôt facile à apprendre, donc je suis surpris qu’on trouve ce livre difficile. Je ne l’ai pas lu, mais je connais un peu le langage, et honnêtement je n’ai que des compliments à en faire.
Janet ressemble à un Lisp 2.0, donc la syntaxe est elle aussi de type Lisp.
Avis sur Lobste.rs
Après 10 mois à utiliser Janet, j’y suis devenu tellement accro que j’ai presque tout oublié des autres langages, à part ceux de la famille APL, et je gère le site de documentation de la communauté tout en écrivant aussi des tutoriels
En moins de 3 semaines, j’avais réécrit tous mes scripts personnels, et j’écris aussi en Janet les nouveaux logiciels d’exploitation que je crée
En pratique, Janet fonctionne presque entièrement comme une table de hachage au niveau de son implémentation, ce qui permet de voir les symboles locaux avec
(keys (curenv))et les symboles du cœur du langage avec(keys (getproto (curenv))); si on veut, on peut même construire quelque chose de proche de CLOS sur cette base, et il existe une implémentationJe fais tourner une vingtaine de sites web et plusieurs services avec le framework web Joy sur un seul VPS gratuit de 512 Mo, et j’ai aussi rédigé un tutoriel à ce sujet
Cela dit, l’expression « collections immuables » est un peu trompeuse par rapport à la réalité, car la bibliothèque standard renvoie le plus souvent des valeurs mutables, donc il n’y a pas aujourd’hui de raison majeure d’insister sur l’immuabilité
La possibilité de faire passer des valeurs du moment de la compilation vers l’exécution m’a paru particulièrement puissante. Par exemple, si on incorpore un fichier biblique
.tsvdans le binaire sous forme de table de hachage à la compilation, l’exécution n’a plus qu’à faire des recherches, et on obtient même des résultats deux fois plus rapides qu’une version Go utilisantembedSi on implémente la même chose à la main en Go avec une table de hachage, le code devient bien plus long, alors qu’en Janet j’ai même pu écrire un compilateur Lisp vers Go en 46 lignes
L’aspect encore plus intéressant mentionné par Ian Henry, c’est que Janet préserve l’état des fermetures entre différentes images/sessions : si on stocke l’environnement concerné dans la table de hachage
(curenv)puis qu’on le restaure dans une nouvelle session REPL, l’état interne des closures continue là où il s’était arrêtéIl y a aussi https://lisp.trane.studio/, une DSL musicale fondée sur Lisp, ainsi que l’article https://dl.acm.org/doi/abs/10.1145/3677996.3678285 et un exemple de résultat à voir ici : https://x.com/greg_ash/status/1824218993118388708
J’ai aussi ma propre bibliothèque, qui fournit une syntaxe de requête proche de SQL au-dessus de plusieurs structures de données
Elle prend en charge l’insertion et la mise à jour de dataframes, ainsi que la sauvegarde/lecture CSV, inclut Datalog et miniKanren, et permet aussi des opérations vectorisées comme en APL
Il y a aussi jnj, qui permet d’utiliser directement J dans Janet, et dans Joy Web Framework on trouve une DSL de requêtes DB comme
(var account (db/find-by :account :where {:login (auth-result :login)})), utilisée aussi dans du vrai code d’authentification de site webQuand on dit « collections immuables », on pense plutôt à des structures de données persistantes, ce qui peut être utile, mais ce n’est pas une capacité de base de Janet
Ce qui me plaît vraiment, c’est la symétrie entre types valeur et types référence, et même si la fin de l’article parle bien de « immutable composite values », si ce n’était pas mon propre texte, je ne suis pas sûr que j’aurais immédiatement compris ce que cela voulait dire
Janet est un langage rafraîchissant et embarqué, donc j’aimerais l’essayer pour le scripting intégré à des projets comme des moteurs de jeu
Il semble bien adapté aux cas où l’on a besoin de hot reload pour itérer rapidement sans remplacer de DLL; Lua reste excellent, mais Janet paraît plus expressif sur certains points
Janet est vraiment un super langage, et j’aimerais un jour l’utiliser comme langage de script dans un projet Zig. Ça fait plaisir de voir davantage de gens parler de Janet
Ça a l’air bien, mais comme je me suis déjà habitué au scripting Clojure avec babashka, la sensation me paraît assez proche. Je me demande s’il y a un avantage majeur qui m’échappe, en dehors du côté embarqué
Des points comme « la déstructuration d’un tableau avec argument de reste peut potentiellement provoquer une copie coûteuse » donnent une impression globalement moins fonctionnelle, et ça ne me plaît pas trop
C’est le genre de fonctionnalité qui me manque ensuite chaque fois que je dois parser du texte dans un autre langage
Janet ressemble moins à un petit Clojure embarqué qu’à un Lua avec une meilleure prise en charge de la programmation fonctionnelle