- La syntaxe de JavaScript devient facilement complexe avec les parenthèses imbriquées et les callbacks, et même une petite UI peut entraîner une inflation en chargeant beaucoup de bibliothèques
- WebAssembly ouvre la voie à l’exécution d’autres langages dans le navigateur, mais le coût de la connexion asynchrone avec la boucle d’événements JavaScript, comme avec Pyodide, reste élevé
- Les ressources du navigateur et la mémoire WebAssembly étant limitées, il faut une approche de négociation plutôt que de chercher à remplacer JavaScript
- LispE embarque plus de 450 fonctions dans un seul binaire WASM de 3,3 Mo, avec chaînes de caractères, calcul, matrices et expressions régulières
- Avec
evaljs et asyncjs, il exploite les fonctions JavaScript et le DOM tout en réduisant le gonflement du code grâce à un binaire unique et auditable au lieu de multiples bibliothèques externes
Le gonflement de JavaScript et les contraintes du navigateur
- La syntaxe de JavaScript devient facilement complexe, car parenthèses, accolades et crochets s’empilent et il faut en respecter l’ordre de fermeture
- Un exemple montre un callback
forEach qui insère des valeurs dans plusieurs objets et met à jour conditionnellement un cache
newNames.forEach((name, i) => {
allAgentContents[name] = contents[i];
agentModes[name] = modes[i];
if (compiled[i]) agentCompiledCache[name] = compiled[i];
agentViewingCompiled[name] = viewing[i];
});
- Pour des traitements qu’un langage de programmation de base inclurait souvent, JavaScript conduit à télécharger de nombreuses bibliothèques
- En C ou en C++, il faut aussi des
include pour travailler, mais avec les bibliothèques JavaScript, il est souvent difficile de savoir qui les a implémentées et qui les maintient
- On voit même des pages qui semblent charger « la moitié d’Internet » juste pour afficher hello dans une petite fenêtre
- Internet dépend de JavaScript et, même si TypeScript en masque une partie, il reste le gardien incontournable à chaque ouverture de page dans le navigateur
- Il y a eu le rêve que le navigateur devienne le système d’exploitation ultime, rendant Windows ou Mac OS superflus, et JavaScript a été choisi comme langage pour concrétiser ce rêve
La relation entre WebAssembly et JavaScript
- WebAssembly s’apparente davantage à une machine virtuelle avec son propre langage machine, ce qui ouvre la possibilité de coder autrement à l’intérieur du navigateur
- Pyodide est une réalisation d’ingénierie impressionnante pour exécuter Python dans le navigateur, mais il met aussi en évidence le coût du fonctionnement à l’intérieur de l’univers JavaScript
- Il faut faire dialoguer deux mondes asynchrones :
asyncio côté Python et la boucle d’événements JavaScript
- Le pont entre ces deux mondes est fragile, et il faut se souvenir à quel monde appartient chaque
await
- Les ressources du navigateur étant limitées, il faut retrouver une manière de penser héritée des débuts de l’informatique, où chaque instruction et chaque structure s’examinent au niveau du bit
- L’auteur prend comme référence le fait d’avoir commencé à programmer en 1981 sur un ordinateur avec exactement 15772 bytes disponibles
- La mémoire des navigateurs modernes n’est pas aussi limitée que ce premier ordinateur, mais WebAssembly ne peut pas posséder la mémoire librement comme un programme classique et doit d’abord l’obtenir selon un mode restreint
- L’attitude centrale n’est pas de se battre contre JavaScript, mais de négocier avec lui
L’alternative proposée par LispE
- LispE fournit plus de 450 fonctions dans un binaire unique
- Le binaire WASM pèse 3,3 MB
- Les fonctionnalités de traitement des chaînes, de calcul, de matrices et d’expressions régulières sont réunies au même endroit
- Le binaire WASM se trouve dans binaries/wasm
- C’est un petit interpréteur, mais il peut manipuler comme valeurs de retour des chaînes, des
Float64Array, des entiers, des nombres réels et des tableaux de chaînes
- LispE est fondé sur Lisp et conserve donc une structure AST vivante
- Si la syntaxe Lisp ne convient pas, on peut utiliser une grammaire transpillée pour un langage difficile à distinguer de Python ou de Basic
- Il est possible de modifier la grammar pour créer un langage au style souhaité
- Cela fonctionne même en grec, avec déjà un exemple en grec
- LispE coopère avec JavaScript au lieu de l’affronter, en s’appuyant sur les fonctions JavaScript et les capacités du DOM
Appels JavaScript : evaljs et asyncjs
- Pour exécuter du code JavaScript depuis LispE, on peut utiliser
evaljs et asyncjs
evaljs exécute du code JavaScript et renvoie une valeur
asyncjs relie des fonctions JavaScript utilisateur définies dans la page à des callbacks asynchrones
(setq a (evaljs "10 + 20 + 30")) ; execute some JS code
; call_llm is a user-defined JS function in the page
(asyncjs `call_llm("Implement a piece of code in Python to sort strings");` 'mycallback)
(defun mycallback(theresult) ...)
asyncjs fonctionne comme une Promise indiquant qu’il reviendra une fois la tâche terminée
Réduire le gonflement du code
- Une citation de Wirth en 1995 mène à la conclusion que des ordinateurs plus rapides et des modèles plus grands ne résolvent pas le problème, et qu’il faut garder le code léger
- LispE expose 450 fonctions au navigateur via un binaire unique et auditable
- Pour implémenter séparément chaque fonction, il faudrait charger des bibliothèques comme
mathjs pour le calcul numérique, lodash pour les collections, voca pour la manipulation de chaînes ou simple-statistics pour les distributions statistiques
- Cette approche peut grossir jusqu’à représenter des centaines de Mo de code, avec des auteurs, des bugs et des calendriers de maintenance différents
- LispE fournit ces fonctionnalités dans un code unique et maintenu, et comme il est open source, l’ensemble du code peut être audité
- L’auteur considère que le Garbage Collector ne fait pas s’effondrer les performances du code au pire moment
- Il communique avec JavaScript de manière transparente via de simples appels d’API et peut renvoyer des structures prédéfinies
// floats here is a Float64Array
const floats = callEvalLispEToFloats(0, `(normal_distribution 100)`);
2 commentaires
L’article semblait sortir de nulle part, donc je doutais de la source, mais apprendre que c’était Naver, c’est assez hallucinant, quoi.
Cela dit, les réactions ne sont effectivement pas bonnes... Honnêtement, vouloir utiliser du Lisp en embarquant même un WASM de 3,3 Mo parce que JavaScript serait soi-disant surestimé, ça ressemble à de l’over-engineering difficile à comprendre, mdr.
Un développeur du projet appelé Claudius a laissé un commentaire au sujet de la raison pour laquelle c’était sous un compte Naver : il travaille chez Naver Labs Europe et Naver l’a approuvé comme projet open source, donc cela a été publié ainsi.
Ça ne semble pas avoir grand-chose à voir avec Naver, on dirait juste des gens qui aiment vraiment le Lisp...
Commentaires sur Lobste.rs
Réduire le bloat de JavaScript en ajoutant un blob WASM opaque de 3,3 Mo et en demandant d’écrire l’app en Lisp ?
Avec du JavaScript pur et zéro dépendance supplémentaire, on peut déjà faire pas mal de choses avant d’atteindre ne serait-ce qu’un dixième de cette taille.
Je n’adhère pas à ce cas d’usage, mais c’est un intéressant projet passion Lisp avec une bizarrerie devenue rare de nos jours, et j’aime bien le style unique de toute la documentation, qui se voit écrite à la main.
Les propositions pour enrichir la bibliothèque standard sont toujours bienvenues.
Parmi les ajouts récents, il y a notamment l’union/l’intersection d’ensembles,
sum,base64, etc.En revanche, j’entends rarement des demandes pour des fonctions de calcul différentiel, et côté chaînes de caractères, les demandes récurrentes portent plutôt sur
.reverseou.titleCase..reversen’a pas vraiment beaucoup d’usages convaincants en dehors des problèmes-jouets, et.titleCaseest possible, mais la discussion est en cours car cela nécessite des données d’internationalisation.En plus, aucune des deux n’existe dans cette bibliothèque
Il y a aussi l’idée que même si on fait une proposition, il faudra 3 ans pour qu’elle soit réellement intégrée, donc que ça ne vaut pas la peine d’essayer, tandis qu’un dossier
utils/n’est pas idéal mais peut être créé tout de suite.Mon propos tient moins à un manque de fonctions en JavaScript qu’au modèle de distribution.
Même avec une bibliothèque standard bien fournie, une application moyenne embarquera encore des dépendances npm transitives en mégaoctets.
LispE-as-WASM, c’est 3,3 Mo, environ 450 fonctions documentées, un cœur en C++, du code source public sur GitHub, et une expérience pour voir à quoi pourrait ressembler un runtime auditable.
J’ai effectivement construit un harnais d’agents par-dessus, et j’exécute à la volée des règles LispE dans le navigateur.
La boucle d’événements JavaScript s’en accommode très bien.
Quand quelqu’un dit que c’est la syntaxe qui est bloatée, ça me met mal à l’aise.
Une syntaxe minimale peut être bien plus difficile à lire qu’une syntaxe qui utilise des caractères différents pour distinguer visuellement les éléments.
Voici ce que LispE propose comme alternative :
... !?
https://github.com/naver/lispe/wiki/5.3-A-la-APL
Le point d’entrée vers ce projet est un peu étrange, et https://github.com/naver/lispe/wiki/1.-Introduction me paraît meilleur.
C’est un projet Lisp natif qui cible aussi le WASM, avec des aspects amusants pour les passionnés de langages de programmation.
Ma première impression, c’était surtout celle d’exemples JavaScript assez forcés.
Une bonne partie de la haine envers JavaScript vient, je pense, des incompatibilités entre navigateurs à ses débuts, des API DOM pénibles et des pièges de syntaxe, et tout cela n’a pratiquement plus d’impact dans mon quotidien aujourd’hui.
Le JavaScript moderne, surtout avec TypeScript et des règles de lint raisonnables, est tout à fait correct en tant que langage.
Il reste bien des problèmes comme CJS contre ESM, les risques de supply chain ou les turbulences de l’écosystème, mais pour l’essentiel, ce sont des problèmes extérieurs au langage lui-même.
Est-ce que cette fameuse expérience, au point que des gens ont créé des extensions d’éditeur rien que pour gérer l’équilibrage des parenthèses, serait donc formidable ?
Si on veut ce style de programmation, c’est-à-dire une faible densité syntaxique et une approche fonctionnelle, il vaut mieux inverser la direction et aller vers un langage concaténatif.
Je me demande comment le résultat compilé d’un interpréteur Lisp peut monter à 3,3 Mo.
C’est ça, « moins de bloat » ?
Cela dit, je suis d’accord que distribuer une grosse bibliothèque standard avec du code inutilisé n’a rien à voir avec le fait de réduire le bloat.
Choc ! Horreur !
Quel étonnement de devoir fermer les parenthèses dans le bon ordre.
Même les amateurs de Lisp qui exècrent la verbosité de langages comme JavaScript font souvent volte-face devant les langages de la famille APL, et se mettent à soutenir que la clarté et la lisibilité comptent bien plus qu’une écriture aussi courte que possible.
L’auteur de LispE semble avoir une certaine affection pour les opérations de base d’APL, mais il est difficile de comprendre pourquoi, après avoir rencontré une notation aussi concise, il préférerait une version de Life de Conway en s-expressions profondément imbriquées, plus longue et plus aérée que les versions APL, J, K ou même Q.