3 Punkte von GN⁺ 2024-05-03 | 1 Kommentare | Auf WhatsApp teilen

Einführung in die Cognition-Programmiersprache

Problemstellung

  • Lisp-Programmierer:innen behaupten, dass sie mit S-Expression-Code und einem funktionalen Makrosystem Metaprogrammierung und verallgemeinerte Systeme bauen können.
  • Aber Lisp hat ein fundamentales Problem.
    • Da Metaprogrammierung und Programmierung nicht dasselbe sind, besitzt Lisp immer eine strikte Syntax (bestimmte Zeichen für Klammern oder Look-ahead).
    • Die linke Klammer teilt Lisp mit, dass es weiterlesen muss, bis die rechte Klammer erreicht ist.
    • Dadurch werden linke und rechte Klammern in der Sprache veränderbar festgelegt (konzeptionell nicht grundsätzlich, aber in manchen Implementierungen nicht möglich).
    • Besonders wichtig ist, dass die Reihenfolge, in der diese Tokens unterschieden werden, ohne String-Verarbeitung nicht nachträglich geändert werden kann.
  • Auch andere Sprachen haben unterschiedliche Verfahren, anhand bestimmter Tokens festzulegen, was als Nächstes gelesen werden soll.
    • Dieser Prozess heißt Syntax.
  • Cognition geht anders vor und verwendet Antisyntax mit vollständiger Postfix-Notation.
    • Das ist ähnlich wie verknüpfte Programmiersprachen, doch diese haben ebenfalls zwei große Probleme:
      1. Einführung von linken/rechten Klammern (eigentlich Präfixnotation)
      2. Das Anführungszeichen für Zeichenketten
    • Das ist für allgemeine Sprachen ungeeignet.
    • Auch in C-Syntax-Implementierungen von Lisp tauchten dieselben Probleme auf (Übermaß an Escape-Zeichen, Erfordernis eines Leerzeichens zur Trennung von Token-Anfang/-Ende).
    • Racket hat zwar ein Makrosystem, ist aber zur Laufzeit nicht dynamisch und nutzt Präprozessoren.

Cognition-Einführung

  • Ein Projekt, an dem ich mehrere Monate gemeinsam mit Matthew Hinton gearbeitet habe.
  • Ziel ist es, mit vollständiger Postfix-Notation eines der am meisten verallgemeinerbaren Syntaxsysteme zu bauen.
  • Das kann Hintergrundwissen zu Grammatik/Tokenisierung/Parsing erfordern, dennoch wird versucht, es verständlich zu erklären.
  • Repository: https://github.com/metacrank/cognition

Baremetal Cognition

  • Baremetal Cognition ist ähnlich wie Brainfuck, erlaubt aber eine robuste Metaprogrammierung.
  • Bootstrap-Codebeispiel:
ldfgldftgldfdtgldf dfiff1 crank f
  • Leerzeichen und Zeilenumbrüche sind wichtig.
    • In der zweiten Zeile steht ein Leerzeichen nach df.
    • In der dritten Zeile befindet sich ein Leerzeichen.
  • Dadurch werden zwei neue Konzepte eingeführt: Delimiter und Ignore.

Tokenisierung

  • Ein Delimiter ermöglicht dem Tokenizer die Unterscheidung von Token-Anfang und -Ende.
  • Die Liste des Single-Character-Tokenizers ist öffentlich, sodass sie innerhalb von Cognition bearbeitet und gelesen werden kann.
  • Ein ignored character wird vom Tokenizer in der ersten Stufe jeder Read-Eval-Print-Schleife vollständig ignoriert.
    • Das heißt, beim Start der Tokenerfassung werden die als ignored Zeichen im Satz übersprungen.
  • Standardmäßig ist jedes Zeichen ein Delimiter und es gibt keine ignored Zeichen.
  • Mit den Delimiter- und Ignore-Listen kann bei gegebenen Zeichen zwischen Blacklist/Whitelist umgeschaltet werden (für Kürze und Praktikabilität).

Falias

  • Falias ist eine Liste von Wörtern, die ausgeführt wird, wenn sie auf den Stack kommt.
  • Jeder Falias führt den Stack-Top aus (entspricht dem eval in Stem).
  • f ist der Standard-Falias. Er wird nicht auf den Stack gelegt und führt den Stack-Top d aus.
  • d konvertiert die Delimiter-Liste auf den Stringwert des Worts (also werden nur die Zeichen l nicht als Delimiter ausgeschlossen).
  • In der Standardumgebung wird kein Wort ausgeführt, außer den speziellen Falias.

Delimiter-Hinweise

  • Es gibt eine interessante Regel für Delimiter:
    • Wenn in der Tokenisierungs-Schleife ein Zeichen nicht ignored ist, bleibt es als Teil des aktuellen Tokens und es wird weitergesammelt.
    • Das ist im Gegensatz zu einem Singlet (womit das Zeichen sich selbst im Token enthält und durch Überspringen die Tokenerfassung beendet).
  • Auch der Blacklist-Status lässt sich setzen.
    • Delimiter-, Singlet- und Ignore-Listen können blacklisted oder whitelisted werden.
    • Standardmäßig gibt es keine blacklisted Delimiter, whitelisted Singlets und whitelisted ignored characters.
  • Alle anderen Zeichen werden als Teil des aktuellen Tokens gesammelt, bis die Tokenisierungs-Schleife durch Delimiter- oder Singlet-Regeln gestoppt wird.

Bootstrap-Code fortgesetzt

ldf
  • l wird zu einem Nicht-Delimiter gemacht.
gldftgldfdtgldf  dfiff1 crank f
  • d ist ein Delimiter, deshalb wird gl auf den Stack gelegt, und der Falias f wird aufgerufen, wodurch gl zu einem Nicht-Delimiter wird.

  • tgl wird auf den Stack gelegt und durch df zu einem Nicht-Delimiter gemacht.

  • dtgl wird auf den Stack gelegt und durch \ndf wird der Zeilenumbruch (\n) zum einzigen Nicht-Delimiter (der tatsächliche Zeilenumbruch ist im Code enthalten).

  • Laut Delimiter-Regeln werden das Leerzeichen und \n auf den Stack gelegt (beinhaltet das Leerzeichen in der dritten Zeile).

  • Es wird noch ein weiteres \ \n-Word tokenisiert.

  • Der aktuelle Stack ist wie folgt (von unten nach oben): 3. dtgl 2. [Leerzeichen]\n

    1. [Leerzeichen]\n
  • df macht \ \n zu einem Nicht-Delimiter.

  • if macht \ \n zu einem ignored Character (wird beim Tokenisierungsstart ignoriert).

  • f führt dtgl aus, wobei der dflag-Umschalter gesetzt wird, der die Unterscheidung zwischen Blacklist und Whitelist für Delimiter speichert.

  • Nun werden alle Nicht-Delimiter zu Delimitern und alle Delimiter zu Nicht-Delimitern.

  • Schließlich werden Leerzeichen und Zeilenumbruch zu Token-Delimitern und beim Tokenstart ignoriert.

  • Als Nächstes wird 1 tokenisiert und auf den Stack gelegt; danach wird das Wort crank tokenisiert und durch f ausgeführt (1-Token gilt hier als Zahl, aber in Cognition ist alles ein Word).

  • Bootstrapping-Sequenz abgeschlossen! Was crank macht, wird im nächsten Abschnitt erklärt.

Bootstrapping-Zusammenfassung

  • Cognition kann die Tokenisierung programmatisch dynamisch ändern.
    • In anderen Sprachen nicht möglich.
    • Für Fremdsprachen kann innerhalb von Cognition der Tokenizer programmiert und die gewünschte Tokenisierung erreicht werden.
  • Durch Postfix-Notation und ohne Look-ahead ist das möglich.
    • Es ist nicht nötig, mehr als ein Token zu parsen, bevor ein Ausdruck ausgewertet wird.
  • Mit Falias kann ein Wort ausgeführt werden, ohne Prefix-Wörter oder Built-ins zu verwenden.

Crank

  • Das metacrank-System erlaubt es, wie Token, die auf den Stack gelegt werden, standardmäßig ausgeführt werden.
  • Das Wort crank nimmt eine Zahl als Argument und führt den Stack-Top aus, sobald auf den Stack n Wörter gelegt wurden.
  • Beispielcode (bei gesetztem crank 1):
5 crank 2
crank 2 crank 
1 crank unglue swap quote prepose def
  • In der Umgebung mit crank 1 ist die Verwendung von f beim Token-Auswerten deaktivierbar.
    • Pro tokenisiertem Token wird eins ausgewertet.
    • Da eine Syntax über Leerzeichen und Zeilenumbruch programmiert wurde, lässt sich der Code intuitiv lesen.
  • Der Code beginnt mit dem Versuch, 5 auszuwerten (da es kein Built-in ist, wertet es sich selbst aus).
  • crank wird so eingestellt, dass es ausgeführt wird, sobald 5 Wörter auf den Stack gelegt wurden.
  • 2crank, 2, crank, 1 werden alle auf den Stack gelegt (weil crank mit crank 5 als Built-in konfiguriert ist und deshalb nicht ausgeführt wird): 4. 2crank 3. 2 2. crank
    1. 1
  • crank ist das fünfte Wort und wird ausgeführt (crank 1 ist gesetzt).
  • unglue ist ein Built-in, das den Wert des obersten Wortes im Stack nimmt, wie durch 1 genutzt.
    • Es nimmt also den Funktionszeiger für das mit crank verbundene Built-in.
  • Der Stack ist jetzt: 3. 2crank 2. 2
    1. [CLIB]
    • CLIB verweist auf den Funktionszeiger des Built-ins crank.
  • swap ausführen: 3. 2crank 2. [CLIB]
    1. 2
  • quote (Built-in, das den Stack-Top quotiert) ausführen: 3. 2crank 2. [CLIB]
    1. [2]
  • prepose (ähnlich compose in Stem, aber vorn abgelegt und so genannt VMACRO) ausführen: 2. 2crank
    1. ([2] [CLIB])
  • def aufrufen
    • definiert das Wort 2crank, das 2 auf den Stack legt und den Funktionszeiger auf das Built-in crank aufruft.
  • Was VMACRO ist sowie der Unterschied zwischen Cognition-Stack und Stem-Stack muss noch erklärt werden.

Unterschiede zu Stem

  • Auf dem Stem-Stack können Wörter direkt auf den Stack gelegt werden.
  • In Cognition wird ein Wort nicht ausgewertet, sondern in einen Container gelegt und auf den Stack gelegt.
    • In Stem arbeitet compose mit einem Word (oder einem Container mit einem einzelnen Word) und einem anderen Container.
    • Das macht die Cognition-API konsistenter.
  • Auch Wörter wie cd nutzen dieses Konzept.

Makros

  • Ein weiterer Unterschied zwischen Stem-Quote und Cognition-Containern.
  • Bei der Makroauswertung wird alles innerhalb des Makros ausgewertet und crank ignoriert.
  • Wenn ein Wort gebunden wird, bei der Auswertung des Wortes

1 Kommentare

 
GN⁺ 2024-05-03
Hacker News-Kommentare

Ein paar zentrale Punkte lassen sich wie folgt zusammenfassen:

  • Der Beitrag stellt die eigentliche Einführung des Cognition-Projekts im Dokument zu spät vor. Es wäre sinnvoll, die wichtigsten Inhalte zuerst zu nennen, um die Zeit der Leser zu sparen.
  • Wie bei den Reader-Layer-Konfigurationsmöglichkeiten von Racket existieren bereits andere Ansätze, die es ermöglichen, die Syntax zu erweitern und zugleich Interoperabilität zu wahren. Es ist jedoch fraglich, ob Cognitions Ansatz grundlegend wirklich „besser“ ist.
  • Auch Common Lisp erlaubt es über Reader Macros, Makros und Compiler-Macros, die Syntax frei zu verändern. Meta-Programmierung dreht sich primär um Semantik, nicht um Syntax.
  • Cognitions Fähigkeit, zur Laufzeit die Syntaxstruktur zu definieren, neu zu definieren und hineinzugehen bzw. wieder herauszukommen, ist elegant und faszinierend. Sie eröffnet die Möglichkeit, eine echte „denkende“ Maschine zu schaffen.
  • Syntax liefert Struktur, daher ist die Abschaffung von Syntax selbst widersprüchlich. Eine zu reduzierte Syntax kann die Lesbarkeit und Verständlichkeit sogar beeinträchtigen.
  • Der Schreibstil des Dokuments wirkt insgesamt etwas ausschweifend und sarkastisch, was das Lesen erschwert. Dennoch behandelt es inhaltlich komplexe Themen.