- Cognition est un projet de recherche sur les langages qui adopte une antisyntaxe entièrement postfixée afin d’éviter le problème de lecture anticipée (read-ahead) des langages Lisp et concaténatifs
- Ses mécanismes clés — delimiter, ignore, singlet, falias, crank et metacrank — permettent à un programme de modifier ses propres règles de tokenisation et son cycle d’exécution
- Le bootstrap démarre dans un état où tous les caractères sont lus comme des tokens uniques, puis migre de lui-même vers un environnement utilisant les espaces et les retours à la ligne comme délimiteurs
cranketmetacrankcontrôlent quand les tokens sont évalués et quand ils sont simplement empilés, ce qui permet de définir dans un système postfixé une syntaxe préfixée comme les commentaires#, l’échappement\, le quote[ou la macro(- Un dialecte Brainfuck est lui aussi implémenté non pas avec un parseur séparé, mais avec les mots Cognition et ses règles de tokenisation, montrant une approche où la grammaire elle-même devient du code et peut être automatisée
Ce que Cognition remet en cause dans les syntaxes classiques
- Lisp offre une métaprogrammation puissante avec les s-expressions et son système de macros, mais reste malgré tout influencé par une syntaxe fixe
- Une parenthèse ouvrante signale qu’il faut continuer à lire jusqu’à rencontrer la parenthèse fermante, ce qui rend difficile — voire impossible dans certaines implémentations — de changer le rôle des parenthèses depuis l’intérieur du langage
- Modifier après coup la manière dont des tokens déjà lus ont été délimités exige beaucoup de traitement de chaînes
- Le processus qui consiste à regarder l’entrée courante et à devoir lire davantage ensuite correspond à la syntaxe ; dès qu’on suppose une lecture anticipée par défaut, on se retrouve lié à certaines formes grammaticales
- Cognition cherche à éviter cela avec une antisyntaxe entièrement postfixée
- Le langage ressemble aux langages concaténatifs, mais considère que même ceux-ci souffrent de problèmes de lecture anticipée à cause des crochets ou des caractères de quote de chaînes
- Le système de macros de Racket est distingué de cela, car il repose sur du prétraitement plutôt que sur des changements dynamiques de syntaxe à l’exécution
Le projet et l’idée de base
- Cognition est un projet de recherche actif mené pendant plusieurs mois avec Matthew Hinton
- Son dépôt d’implémentation est cognition-rust, qui contient aussi un article sur le langage
- Des bases en parsing, tokenisation et grammaire aident à sa compréhension
- L’explication part du code de « baremetal cognition » pour suivre son évolution vers une syntaxe proche de Stem
Baremetal Cognition et la tokenisation
- baremetal Cognition ressemble en apparence à Brainfuck, mais permet une métaprogrammation bien plus poussée
- Son code de bootstrap minuscule utilise même les espaces et les retours à la ligne de manière signifiante, et dans son état initial chaque caractère isolé est lu comme un token
- Cognition repose fondamentalement sur une architecture à pile, mais emploie le terme container dans un sens plus général que stack
- Dans l’environnement de base, aucun mot ne s’exécute automatiquement, à l’exception de certains falias spéciaux
-
delimiter, ignore, singlet
- delimiter permet au tokenizer de savoir où se termine un token et où commence le suivant
- La liste des delimiters d’un seul caractère peut être modifiée et lue depuis le code Cognition lui-même
- Les caractères ignore sont sautés au début de la phase de collecte des tokens de chaque boucle read-eval-print
- Par défaut, tous les caractères sont des delimiters et il n’y a aucun caractère ignore
- Les listes delimiter, singlet et ignore peuvent changer de comportement via des drapeaux whitelist/blacklist
- La configuration par défaut est : aucun delimiter blacklisté, aucun singlet whitelisté et aucun caractère ignore whitelisté
- Un singlet constitue une troisième catégorie de tokenisation : il s’inclut lui-même dans le token puis met fin à la collecte du token
-
falias
- falias désigne la liste des mots exécutés immédiatement dès qu’ils sont placés dans un container
- Le falias par défaut
fne s’empile pas dans le container et exécute à la place le mot au sommet du container - Dans l’exemple, quand
fexécuted,dremplace la liste des delimiters par la valeur de chaîne du mot - Le bootstrap continue ensuite en transformant progressivement des caractères comme
l,g,tetden non-delimiters, puis en faisant des espaces et retours à la ligne des delimiters et des caractères ignore
L’environnement d’exécution construit par le bootstrap
- Le bootstrap initial modifie les règles de delimiter et d’ignore pour aboutir à un environnement où les espaces et les retours à la ligne servent de délimiteurs de tokens et sont ignorés au démarrage de la collecte d’un token
- Il lit ensuite
1etcrank, puisfexécutecrankpour entrer dans un environnement crank 1 - L’idée centrale du processus est qu’on peut changer la manière de tokeniser pendant l’exécution
- Les modifications de delimiter, singlet et ignore peuvent être automatisées par programme
- Comme tout est postfixé et sans lecture anticipée, il n’est pas nécessaire de parser à l’avance un ou plusieurs tokens avant d’évaluer une expression
- falias permet aussi d’exécuter certains mots sans mots préfixés ni exécution automatique des mots de base
crank et metacrank
- crank est un mécanisme qui détermine à quel rythme les tokens posés sur la pile sont exécutés
- Le mot
crankprend un nombre en argument et exécute ensuite le sommet de pile à chaquen-ième mot ajouté au container- Le
1 crankfinal du bootstrap crée un environnement qui évalue tous les tokens - En mode
5 crank, les mots peuvent s’accumuler jusqu’à l’arrivée du cinquième token
- Le
- Le code d’exemple utilise
unglue,swap,quote,preposeetdefpour créer un mot nommé2crankungluerécupère la valeur d’un mot, y compris le pointeur de fonction d’un builtin commecrankpreposeressemble aucomposede Stem, mais ajoute au début et place le résultat dans VMACROdefdéfinit2crankde façon à poser2sur la pile puis à appeler le builtincrank
-
Les containers et les macros dans Cognition
- Dans Stem, on peut poser directement des mots sur la pile, alors que dans Cognition les mots non évalués vont dans un container
- Cette conception permet à un mot comme
composede traiter avec une API cohérente aussi bien un container d’un seul mot qu’un autre container plus complexe - Les macros de Cognition sont différentes des quotes de Stem
- Quand une macro est évaluée, elle ignore crank et tous ses éléments internes sont évalués
- Si l’on évalue une macro liée à un mot, la macro entière s’exécute indépendamment de crank et le cranker n’est incrémenté qu’une seule fois
- Les macros sont utiles pour du code indépendant de crank et pour des expansions à visée d’optimisation
-
metacrank
n m metacrankdéfinit une évaluation périodique de périodempour l’élément situénpositions sous le sommet de pilecrankéquivaut à0 m metacrank- Un seul metacrank peut être évalué par token, et les metacrank plus bas ont la priorité
- metacrank et crank s’appliquent non seulement aux tokens d’un fichier, mais aussi au processus récursif d’évaluation des définitions de mots
- metacrank permet une manipulation directe de la grammaire, par exemple pour dire « après avoir lu
ntokens, je veux exécuter ce token » - On peut programmer des mots préfixés puis les
undefune fois qu’ils ne sont plus nécessaires - On peut aussi créer des caractères préfixés qui s’arrêtent après un certain nombre de tokens plutôt qu’à un caractère de fin donné
- Une entrée utilisateur peut être envoyée vers un programme mathématique puis son résultat transmis à un système grammatical comme metacrank
Évolution vers un dialecte Stem
- Après son bootstrap, Cognition construit progressivement depuis l’intérieur du langage une syntaxe proche de Stem v2
- Il retire d’abord
fde la liste falias pour ne laisser queing- Comme
fs’exécuterait si on le plaçait directement sur la pile, le code créeffpuis coupe la chaîne en deux pour obtenir deuxf - Ensuite,
fest défini comme un mot vide correspondant à false
- Comme
-
Commentaires
#- Le caractère
#constitue le premier exemple de code se comportant comme un véritable préfixe - Ce caractère de commentaire fonctionne comme un préfixe qui jette le texte jusqu’au retour à la ligne, créant ainsi une syntaxe où le parseur doit regarder devant lui
- L’implémentation combine
geti,getd,gets,crankbase,halt,VMACRO cast,singlet,delim, etc. geti,getdetgetsrécupèrent respectivement ignore, delimiter et singlet sous forme de chaîneshaltremet tous les metacrank à 0VMACRO castconvertit le container au sommet de pile en macro- La définition de
#modifie les règles de tokenisation, appelle ensuite#sur les mots qui seront tokenisés à l’avenir, jette ce commentaire, puis revient au crank et aux metacrank d’origine
- Le caractère
-
Échappement, quote, macro
\est défini comme un caractère d’échappement qui permet de placer sur la pile un mot qui serait autrement évalué- Une définition de quote
[est ensuite ajoutée, puis redéfinie à l’aide de ce quote pour permettre un quote récursif plus robuste - Grâce au
defpostfixé, on peut utiliser une définition précédente pour en construire une nouvelle - Ce schéma est une manière de développer fréquemment employée en Cognition bas niveau
(est défini comme délimiteur de macro- Les macros sont conçues pour s’étendre automatiquement, et il est jugé plus efficace de lier à un mot une macro déjà développée
- Fonctionnellement, l’évaluation reste la même
expanddéveloppe récursivement viaunglueles définitions de mots à l’intérieur d’un quote ou d’une macro- Le code définit d’abord un
expandbasique, puis le redéfinit en s’appuyant sur lui-même pour couvrir des cas plus généraux
Dialecte Brainfuck
- Cognition définit un dialecte Brainfuck au-dessus de son dialecte Stem évolué
- L’exemple d’exécution est
../crank -s 2 bootstrap.cog helloworld.bf brainfuck.cog brainfuck.cogn’est pas un parseur Brainfuck au sens habituel- Il définit des mots Brainfuck
- Il tokenise Brainfuck
- Il l’exécute dans l’environnement natif de Cognition
- Cet exemple montre qu’avec la grammaire de Cognition, on peut facilement définir des grammaires alternatives
- Au lieu de lire des symboles puis de décider d’une action en fonction d’eux, Cognition définit les caractères préfixés eux-mêmes comme des mots à l’aide de metacrank, ce qui fait entrer la grammaire directement dans les définitions de mots
L’idée de dialect dialect
- On peut imaginer un mot comme
mkprefix- Par exemple, un mot qui prend deux mots d’entrée
[et]ainsi qu’une opération, puis définit automatiquement[pour appliquer cette opération jusqu’à rencontrer]
- Par exemple, un mot qui prend deux mots d’entrée
- Une telle idée est possible parce que
metacranketdefsont tous deux de simples mots ordinaires - Comme
d,ietssont eux aussi des mots, il devient possible de créer des dialectes plus abstraits qui automatisent l’implémentation de la grammaire - Rien de cela n’est encore implémenté dans la bibliothèque standard, mais Matthew Hinton et l’auteur ont évoqué plusieurs pistes pour une future bibliothèque standard
- un metaword qui génère et appelle automatiquement des mots abstraits
- une recherche de word-generator capable d’abstraire automatiquement la wordlist actuelle
- une manière d’indiquer à un framework d’abstraction comment résoudre un problème
La possibilité de traiter la grammaire comme du code
- Dans Cognition, le traitement des chaînes équivaut à un post-traitement du tokenizer, ce qui donne une portée particulièrement forte aux opérations sur les chaînes
- Parmi les applications potentielles citées figurent la Symbolic AI, la recherche sur la syntaxe et la grammaire, ainsi que les expériences de prototypage autour des langages et métalangages
- Le texte évoque aussi des idées comme un programme lisant des fichiers de configuration, un shell basé sur Cognition ou même un système d’exploitation basé sur Cognition
- L’idée centrale est que Cognition rend possible le « syntax as code »
- la grammaire peut être programmée dynamiquement
- la génération même de la grammaire peut être automatisée
- Des concepts comme Metastack ou
cdne sont pas abordés ici et sont laissés comme sujets possibles pour un billet ultérieur
1 commentaires
Avis sur Hacker News
Par exemple, dans Racket, on peut créer une implémentation Datalog intégrée qui utilise la syntaxe Datalog tout en interopérant avec d’autres modules Racket, sans modifier le modèle de données de base
C’est une façon de faire de la métaprogrammation sans être enfermé dans les S-expressions, tout en la traitant à un niveau plus élevé
Ce bootstrap syntaxique est élégant et intéressant pour la recherche, mais je ne sais pas s’il est fondamentalement meilleur que l’approche de Racket
Les macros Lisp, Scheme et Racket opèrent généralement sur un AST, mais Rhombus travaille sur une structure appelée « shrubbery », proche d’un AST tout en repoussant certaines décisions de parsing à plus tard, ce qui donne un peu de souplesse pour les extensions syntaxiques
Références : https://docs.racket-lang.org/guide/hash-reader.html, https://docs.racket-lang.org/datalog/datalog.html, article sur Rhombus https://doi.org/10.1145/3580417
#langde Racket plus facile à utiliser que les readtables de CLLes readtables seules sont assez puissantes pour implémenter un compilateur C : https://github.com/vsedach/Vacietis
Personnellement, j’ai éclaté de rire au passage où « metacrank » apparaît
En Emacs Lisp, Common Lisp et ISLISP, les macros reçoivent simplement des données et renvoient simplement des données ; il n’y a pas de notion d’AST
Quand on appelle
(foo-macro ...),...peut être n’importe quelles donnéesPar exemple,
(defmacro rev (&rest items) (reverse items))ne fait que prendre la liste des arguments source de l’appel de macro et l’inverserOn peut l’utiliser comme
(rev 1 2 3 4 +)ou(rev (rev 10 n -) (+ a 20 b) (rev 30 a *) list), et ce sont en réalité des listes, des nombres et des symboles qui sont transmisCe n’est ni du texte ni un AST, et cela fonctionne de la même manière si l’on passe des données calculées à
evalLe reader de Lisp lit fondamentalement une couche de données, les expressions symboliques, et EVAL, les macros et les autres fonctionnalités reçoivent principalement des données
Le compilateur peut créer en interne une représentation AST, mais c’est un choix d’implémentation ; le langage Lisp est généralement défini sur une syntaxe de données plutôt que sur une syntaxe textuelle
Un interpréteur Lisp est un « List Processor » qui traite des S-expressions à l’exécution, pas du texte, et
COMPILEreçoit lui aussi des S-expressions, pas du texteRacket et Scheme disposent de systèmes de macros distincts
Il y a plus de 300 mots avant que Cognition, le projet réel, ne soit mentionné ; les considérations sur Lisp sont intéressantes, mais je me demande si c’est vraiment la partie la plus importante du projet
Quand on lit un texte informatif, on se demande constamment « est-ce que ça vaut mon temps ? » ; il faut donc dire dès le départ de quoi parle le document
Quelque chose comme « Cognition est un nouveau langage qui explore une syntaxe modifiable par l’utilisateur » aurait suffi, mais même après les quatre premiers paragraphes, il était difficile de juger si cela valait la peine de continuer
Si le temps, c’est de l’argent, alors on peut considérer que le temps passé à lire cet article a été perdu
Plutôt que d’attendre que tous les contenus d’Internet soient adaptés à ses préférences personnelles, je pense qu’il vaut mieux s’adapter aux formats existants
Le texte n’est pas un média qui doit être consommé uniquement de façon séquentielle comme la vidéo : on peut le parcourir en diagonale pour trouver les passages intéressants et le jeter s’il n’y en a pas, ou revenir au début et lire s’il y en a
La diversité des styles d’écriture est préférable, car elle oblige à filtrer consciemment les informations que l’on consomme ; consommer uniquement passivement rend l’esprit paresseux
Cela dit, j’aurais été d’accord s’il s’était agi d’une vidéo
Avec une vidéo, il faut décider avant de regarder si l’on va y investir du temps, et le 2x ou les sauts de 5 à 10 secondes aident un peu, mais ne résolvent pas le problème
La structure commence par expliquer le problème, puis présente la solution
Après seulement quelques phrases, on comprend que c’est une solution donquichottesque à un « problème » dont 99,999 % des gens, y compris ceux comme moi qui ont entendu parler de Lisp mais ne l’ont jamais utilisé en dehors des fichiers de configuration d’Emacs, n’ont rien à faire ; mais j’ai tout de même continué à lire
Sans ce passage, la suite aurait été plus difficile à comprendre
Si l’on ne connaît pas ce contexte, on peut passer à côté du sens de tout l’article, et il est aussi difficile de savoir s’il s’agit d’un argument de l’homme de paille
Du coup, l’ensemble donne l’impression d’exister pour un besoin très étroit, alors que le titre paraît beaucoup plus général et présente un concept assez intéressant
Les deux premières phrases font apparaître le problème qu’il cherche à résoudre, et c’est beaucoup plus utile que l’introduction proposée pour évaluer mon intérêt
Cela dit, personnellement, quand je regarde l’échelle de la pureté en programmation, Forth est à peu près la limite de pureté philosophique que je peux supporter
Nous continuerons à traiter encore davantage de magie noire à l’avenir
Mais presque tous les langages, y compris tous les Lisp, gèrent mal la citation, et, assez ironiquement,
m4fait exceptionLisp contourne ce problème avec les macros, en permettant de manipuler des phrases du métalangage exprimées sous forme de phrases du langage objet en ignorant la citation
Ce problème vient du fait que, dans le langage objet comme dans le métalangage, l’espace est traité comme la fin d’un atome, sans distinction entre les deux
L’approche de Cognition, une antisyntaxe entièrement postfixée, ressemble aux langages de programmation concaténatifs, mais les langages postfixés sont les duaux des langages préfixés et souffrent du même problème
Il faut soit fixer à l’avance l’arité de tous les symboles et ne pas utiliser de fonctions d’ordre supérieur, soit disposer d’une paire de délimiteurs capable de sérialiser un arbre
S’appuyer sur une pile implicite d’ordre 0, c’est un peu comme pratiquer une lobotomie frontale pour soigner une dépression
Nous ne savons pas nous-mêmes à quel point ce que nous avons fait est nouveau, et si tu penses que ce que nous faisons peut être fait en Lisp, n’hésite pas à le démontrer
m4L’affirmation est intéressante en soi, mais il faudrait quelque chose de plus concret
De même qu’une lobotomie réduit les capacités de traitement de haut niveau, revenir à la forme la plus primitive du calcul par chaîne de commandes peut être vu comme similaire
https://www.hpmuseum.org/rpnvers.htm
J’aime particulièrement le fait que ce mécanisme soit très petit
Je ne suis pas spécialiste des langages, donc je ne sais pas s’il y a quelque chose de vraiment nouveau, mais tout au long de la lecture, on sentait la joie des auteurs découvrant, à chaque colline franchie, une nouvelle chaîne de montagnes de possibilités
Si j’ai bien compris, cela veut dire qu’avec Cognition on pourrait vraiment construire une machine pensante
Sans que le programme doive être interrompu puis redémarré avec de nouvelles commandes, il peut écrire et exécuter lui-même de nouvelles sous-routines à partir de nouvelles entrées
Autrement dit, le programme peut apprendre et s’adapter en créant de nouvelles abstractions et en se connectant à de nouvelles API
Pour moi, c’est plus intéressant que des réseaux de neurones plus gros ou de nouvelles techniques d’apprentissage
Common Lisp possède des reader macros, qui permettent de modifier la syntaxe comme on le souhaite, et il existe même un compilateur Fortran qui lit la syntaxe Fortran avec des reader macros
Common Lisp dispose de reader macros au moment de la lecture, de macros, et de compiler macros au moment de la compilation, et tous ces langages de macros sont Common Lisp
La métaprogrammation n’a pas grand-chose à voir avec les macros ou la syntaxe ; elle désigne la capacité à manipuler la sémantique et les significations de types, interfaces, classes, méthodes, etc.
Si CL lui-même n’est pas assez puissant pour cela, il existe CLOS, c’est-à-dire le Common Lisp Metaobject Protocol
Avec les reader macros de CL, on peut utiliser un autre tokenizer, mais il faut signaler le changement de tokenizer par une expression dans la read table
Dans Cognition, appeler une fonction semble changer le tokenizer dans le contexte de l’appelant
La raison pour laquelle on nous a appris que ce genre de travail était important avec des machines de Turing ou le lambda-calcul, c’est qu’il montre qu’un langage de haut niveau est équivalent au langage fondamental, et donc que ce que l’on démontre sur le premier peut aussi s’appliquer au second
Le premier et seul exemple qui me vient à l’esprit est le problème de l’arrêt
À une échelle pratique, si l’on prouve que le langage de base n’a pas de fuites mémoire, peut-on dire que les langages dérivés n’en ont pas non plus ?
Je me demande quels sont les avantages de ce type de bootstrap
Si la réponse est simplement, comme pour l’ascension de l’Everest, « parce qu’il est là », je respecte aussi cela
df, l’espace à la ligne 3 et le saut de ligne qui sont importants, j’ai immédiatement pensé « merci, mais non merci »Les trois espaces en fin de ligne précédente indiquent du sarcasme ; là où les espaces de fin de ligne ne sont pas faciles à distinguer, on peut les interpréter littéralement
La partie de bootstrap mentionnée est précisément le passage où l’on indique au lecteur de traiter les espaces et les sauts de ligne comme des délimiteurs
Autrement dit, cela revient à se plaindre que l’espace a un sens dans une section où on le déclare comme délimiteur
Bien sûr, tu as le droit de le voir ainsi, mais je me demande s’il existait une meilleure façon de faire
Je ne vois pas comment faire cela sans qu’un espace littéral prenne au moins une fois ce sens de cette manière
Pensez-vous pouvoir lire une phrase comme « phrase cette sans vous syntaxe lire pouvez » sans syntaxe ?
Cognition utilise peut-être une antisyntaxe entièrement postfixée, mais le postfixé reste une syntaxe
Il suffit de demander à un germanophone ce qu’il pense des verbes placés en fin de phrase
Même dans le premier exemple, l’ordre des opérandes et des opérateurs compte, et c’est précisément cela, la syntaxe
Cela ressemble à une tentative de créer un langage absurdement condensé, et cela me rappelle beaucoup APL
Petit indice pour les auteurs : vous n’avez pas supprimé la syntaxe, vous l’avez seulement rendue difficile à lire et à comprendre pour les humains, or la lisibilité et l’intelligibilité sont des éléments importants en programmation
J’avais l’impression que les règles changeaient constamment sous mes pieds, avec des règles et des mots introduits puis redéfinis arbitrairement
L’ensemble a une ambiance très Numberwang, ce qui explique en partie pourquoi cela peut être reçu comme une satire
Une autre grande raison est que l’étape de bootstrap est écrite de manière ridiculement absurde, mais cela semble intentionnel
Il y a clairement quelque chose de profond là-dedans, mais il faudra que je relise après un café plus corsé
Je suis l’auteur de cet article, et le problème est qu’il y a vraiment beaucoup de contenu à transmettre
Matthew et moi avons échangé plusieurs heures par jour pendant trois semaines sur la conception de ce langage, et il y a aussi beaucoup de contexte à fournir à des personnes qui ne me connaissent pas vraiment