Lisp: Zuckerguss oder Kuchen?
(dthompson.us)- Beim Spring Lisp Game Jam 2024 wurden 48 Spiele eingereicht – ein neuer Rekord. Die Beiträge teilen sich deutlich in zwei Ansätze: Lisp als Schicht obenauf und Lisp als Stack selbst
- Der Zuckerguss-Ansatz legt Lisp als Scripting-Schicht über Programme auf Basis von C/Rust/Lua und liefert schnell Ergebnisse, ist aber stark an die darunterliegenden statischen Sprachen und Toolchains gebunden
- Der Kuchen-Ansatz schreibt den Großteil des Programms in Lisp und minimiert C-FFI, was tiefere Kontrolle ermöglicht, aber höhere Kosten bei Bibliotheksimplementierung, Wrappern und Web-Deployment verursacht
- Im Game Jam sind Fennel+love2d und S7+raylib eher Zuckerguss, Guile+Chickadee ist Kuchen, und Hoot+HTML5 canvas ist dank einer Scheme-basierten Wasm-Toolchain näher am Kuchen
- Je größer der Lisp-Anteil wird, desto mehr nehmen Live-Hacking, Speichersicherheit, weniger Lisp/C-Grenzen und Hackbarkeit zu; Projekte wie Guix, Trial und Pre-Scheme zeigen dieselbe Richtung
Einreichungen beim Spring Lisp Game Jam 2024
- Der Spring Lisp Game Jam 2024 endete vor einer Woche, und mit 48 eingereichten Spielen wurde ein neuer Jam-Rekord aufgestellt
- Danach spielten und bewerteten die Teilnehmenden eine Woche lang gegenseitig ihre Spiele
- Die Verteilung der Einreichungen nach Sprache sieht wie folgt aus
- Guile: 15, 31 %
- Fennel: 10, 21 %
- Clojure: 5, 10 %
- Common Lisp: 5, 10 %
- Racket: 4, 8 %
- Elisp: 4, 8 %
- S7: 3, 6 %
- Kawa: 1, 2 %
- Owl: 1, 2 %
- Anteil von Guile: {p:31}
- Der Grund, Scheme-Implementierungen nicht in einer einzigen Kategorie
schemezusammenzufassen, ist, dass die Scheme-Spezifikation klein ist und Guile, Racket, S7 und Kawa Implementierungen mit unterschiedlichen Zielen sind - Bei diesem Jam hatte Guile erstmals die meisten Einreichungen
- 11 der 15 Guile-Spiele sind Web-Spiele, die mit Hoot erstellt wurden
- Hoot ist ein Scheme-to-WebAssembly-Compiler, der vom Spritely Institute entwickelt wird
- 2 der 11 sind offizielle Spritely-Projekte
- Das Spritely Institute hatte vor Beginn des Jams dazu aufgerufen, Spiele mit Hoot zu bauen, und viele Teilnehmende folgten dem
- Normalerweise ist Fennel die beliebteste Sprache in diesem Jam; Fennel ist ein Lisp, das nach Lua kompiliert
- Auch die drei Spiele mit S7 sind Beispiele dafür, wie Lisp in der Spieleentwicklung eingesetzt wird
Lisp als Zuckerguss verwenden
- Das Zuckerguss-Muster ist ein Ansatz, bei dem Lisp als Scripting-Sprache auf einen „Kuchen“ aus statischen Sprachen wie C oder Rust gelegt wird
- Meist wird ein Lisp-Interpreter in ein größeres Programm eingebettet
- Wenn man die High-Level-Teile einer Anwendung in Lisp schreiben möchte, kann das der schnellste Weg sein
- Man braucht einen passenden Interpreter oder Compiler
- Außerdem muss es eine Möglichkeit geben, die nötigen Hooks in die Anwendung einzubauen
- Wenn der Hauptteil des Programms in C oder Rust geschrieben ist, kann man ihn mit emscripten nach WebAssembly kompilieren und im Web deployen
- Man kann schnell zufriedenstellende Ergebnisse erzielen, ist aber stark an die statische Sprache und deren Toolchain gekoppelt
- Typische Beispiele sind
- S7 ist ein einbettbares Scheme
- Guile kann ebenfalls zur Erweiterung von C-Programmen genutzt werden, wird aber normalerweise dynamisch gegen
libguilegelinkt, statt den Interpreter in die ausführbare Datei zu packen - Fennel nutzt bestehende Anwendungen mit Lua-Erweiterungspunkten und kompiliert eine Lisp-artige Sprache nach Lua
Lisp als Kuchen verwenden
- Das Kuchen-Muster ist ein Ansatz, bei dem möglichst viel des Software-Stacks in Lisp implementiert wird
- Statt Lisp in ein Nicht-Lisp-Programm einzubauen, wird der Großteil des Programms in Lisp geschrieben
- Bei Bedarf werden Shared Libraries über ein Foreign Function Interface (FFI) aufgerufen, doch dieser Einsatz sollte möglichst gering bleiben
- Bis Ergebnisse sichtbar werden, dauert es länger
- Bibliotheken, die in der gewählten Lisp-Implementierung fehlen, müssen selbst implementiert werden
- Für unvermeidbare C-Shared-Libraries müssen Wrapper geschrieben werden
- Web-Deployment wird schwieriger, weil das Projekt nicht ohne Weiteres ein emscripten-Ziel ist
- Dieser Ansatz berührt die klassische Debatte embed vs. extend
- Guile kann auch als Zuckerguss dienen, spielt seine Stärken aber eher als Kuchen aus
- Die ursprüngliche Vision von Guile war, andere Programme durch Hinzufügen eines Scheme-Interpreters Emacs-ähnlich zu machen
- Die heutige gute Praxis ist, Programme von Anfang an in Scheme zu schreiben
- Auch Common Lisp ist ein gutes Beispiel für den Kuchen-Ansatz
- Implementierungen wie SBCL bieten ein gutes C-FFI
- Sie können zu effizienten nativen Executables kompiliert werden, was Situationen reduziert, in denen man aus Performance-Gründen zu C greifen möchte
Zuckerguss und Kuchen anhand von Game-Jam-Beispielen
-
Fennel + love2d
- love2d ist seit Langem eine beliebte Wahl für die Spieleentwicklung durch Einzelpersonen oder kleine Teams
- love2d ist ein C++-Programm mit eingebettetem Lua-Interpreter und damit ein gutes Ziel für Fennel
- Da die meisten Linux-Distributionen love2d paketieren, lassen sich
.love-Dateien leicht nativ ausführen - Dank emscripten lassen sich love2d-Spiele auch im Web bereitstellen
- Deshalb nutzen die meisten Fennel-Spiele love2d
- ./soko.bin und Gnomic Vengeance verwenden diesen Stack
- Fennel+love2d ist ein vollständiges Beispiel für Lisp as icing
- Fennel sitzt ganz oben im Stack, und es gibt praktisch keinen Weg, Lisp in die unteren Schichten auszubreiten
- Bis heute ist es der erfolgreichste Lisp-Stack für Spieleentwicklung
-
S7 + raylib
- In diesem Jam nutzten zwei Spiele den Stack S7+raylib: GhostHop und Life Predictor
- Raylib ist eine C-Bibliothek mit Bindings für mehrere High-Level-Sprachen und ist in den letzten Jahren beliebter geworden
- Da auch S7 in C implementiert und leicht einzubetten ist, lässt sich diese Kombination gut mit emscripten im Web bereitstellen
- Auch S7+raylib ist ein Beispiel für Lisp as icing, und es wird interessant sein zu sehen, ob es in künftigen Jams populärer wird
-
Guile + Chickadee
- Chickadee ist eine Spielebibliothek für Guile und implementiert fast alle interessanten Teile einschließlich Rendering in Scheme
- In den jüngsten Jams wurden zwei Spiele mit Chickadee erstellt: Turbo Racer 3000 und Bloatrunner
- Guile+Chickadee ist ein Beispiel für Lisp as cake
- Chickadee umschließt einige C-Bibliotheken für Low-Level-Aufgaben wie das Laden von Bildern, Audio und Fonts, der eigene Code ist jedoch in reinem Scheme geschrieben
- Auch Matrix- und Vektormathematik sind vollständig in Scheme implementiert
- Es bietet einen Satz an Rendering-Primitiven, der mit love2d und raylib vergleichbar ist, ebenfalls in Scheme implementiert
- Während andere Lisp-Spielebibliotheken häufig C-Bibliotheken wie nanosvg verwenden, hat Chickadee auch Fortschritte bei der Implementierung von Vektorgrafik-Rendering in Scheme gemacht
- Chickadee hat die Grenzen des Guile-Compilers und der virtuellen Maschine ausgereizt, wodurch auch Guile verbessert wurde
- Da die Entwicklung jedoch größtenteils von einer Person in begrenzter Freizeit vorangetrieben wurde, dauert es lange, Funktionsparität mit populäreren Spieleentwicklungsbibliotheken zu erreichen
- Schon im aktuellen Zustand funktioniert es für diesen Zweck ziemlich gut
-
Hoot + HTML5 canvas
- Hoot ist ein Scheme-to-WebAssembly-Compiler
- Hoot kompiliert nicht die in C geschriebene Guile-VM per emscripten nach Wasm
- Stattdessen implementiert es eine vollständige Wasm-Toolchain und ein neues Backend für den Guile-Compiler, das direkt Wasm ausgibt
- Hoot ist vollständig in Scheme geschrieben
- Anders als C-Programme, die mit emscripten kompiliert werden und linearen Speicher nach Wasm 1.0 anvisieren, zielt Hoot auf Wasm 2.0 mit GC-verwalteten Heap-Typen
- Dank dieser Architektur liefern Hoot-Binaries keinen Garbage Collector mit
- Dadurch sind sie deutlich kleiner als Lisp-Runtimes, die mit emscripten kompiliert wurden
- Das Wasm-Binary eines Hoot-Spiels ist kleiner als 2 MiB, während das geprüfte
love.wasmeines love2d-Spiels fast 6 MiB groß war - Hoot-Programme interagieren einfach mit JavaScript
- Scheme-Objekte können leicht an JavaScript übergeben werden
- JavaScript-Objekte können ebenfalls an Scheme übergeben werden
- Denn die Objekte beider Seiten werden im selben Heap verwaltet
- Browser-APIs lassen sich über Wasm-Imports ansprechen, sodass die integrierte HTML5-canvas-API für Spiele eine einfache 2D-Rendering-Option ist
- In diesem Jam gab es 11 Spiele mit Hoot, darunter Cirkoban und Lambda Dungeon
- Hoot+HTML5 canvas ist größtenteils ein dicker Kuchen mit etwas Zuckerguss
- Hoot zum Booten zu bringen kostete ein Jahr und erhebliche finanzielle Mittel
- Es nutzt nicht emscripten, sondern eine eigene Toolchain, und auch der Guile-Compiler wurde erweitert
- Es gibt außerdem einen Wasm-Interpreter, der auf der Guile-VM läuft
- Die canvas-API hingegen ist sehr High-Level
- Ein kuchen-näherer Ansatz wäre, über Hoots JS-FFI WebGL oder WebGPU aufzurufen
- Die künftigen Pläne gehen in Richtung WebGL/WebGPU; um das zu ermöglichen, sind Verbesserungen an Wasm GC nötig
- Ein weiteres Ziel ist, Chickadee auf Hoot zu portieren, damit Chickadee-Spiele ähnlich wie love2d-Spiele leicht nativ und im Browser spielbar werden
Grenzen und Vorteile des Kuchen-Ansatzes
- Auch der Kuchen-Ansatz hat klare Grenzen
- Die heutige Umgebung ist nicht die Welt der Lisp-Maschinen, und selbst der höchste Lisp-Kuchen steht meist auf einem größeren Kuchen aus C
- Moderne Lisp-Systeme erreichen an irgendeinem Punkt die darunterliegenden Schichten
- Emacs steht auf einem C-Core
- Die Guile-VM ist in C geschrieben
- Hoot läuft auf großen C++-basierten JavaScript-Engines wie V8
- Hoot-Spiele rendern derzeit mit HTML5 canvas statt mit WebGL/WebGPU
- Für die Nutzung von OpenGL braucht man
libGL - Chickadee verwendet
guile-opengl, daslibGLüber C-FFI aufruft - Auch
libpng, FreeType und andere existieren
- Alles in Lisp neu zu schreiben, ist vor allem eine Ressourcenfrage
- Trotzdem ist es ein kleiner Sieg, Teile des Stacks von Sprachen wie C zurückzuerobern
- Die in Lisp geschriebenen Teile sind leichter zu hacken, und einige erlauben sogar Live-Hacking während der Programmausführung
- Dank GC-verwalteter Runtimes erhält man in der Regel Speichersicherheit
- Weniger FFI-Aufrufe verringern den Overhead beim Überschreiten der Lisp/C-Grenze und erhöhen auch die Sicherheit
- Je größer der Lisp-Anteil im Stack wird, desto näher kommt er dem Kuchen statt dem Zuckerguss
Kuchen-Beispiele außerhalb von Spielen
- Guix ist ein gutes Beispiel dafür, wie mächtig der Kuchen-Ansatz werden kann
- Guix übernimmt das funktionale Packaging-Modell des Nix-Projekts und implementiert es neu, wobei die Nix-Sprache durch Guile ersetzt wird
- Der Grund dafür sind Code-Staging, Code-Sharing und bessere Hackbarkeit
- Guix nutzt außerdem ein in Guile geschriebenes Init-System statt systemd, und auch diese Entscheidung beruht auf denselben Gründen
- Anfangs war Guix leicht als unnötige Neuerfindung des Rads zu kritisieren, doch zehn Jahre später ist das Beharren auf maximaler Lisp-Nutzung ein Kern des Projekterfolgs
- Wer Guix-Idiome und etwas Guile lernt, erhält eine starke Fähigkeit, das Betriebssystem nach eigenen Vorstellungen zu konfigurieren
- Guix kann als die Erfahrung gelten, die einer Lisp-Maschine auf moderner Hardware am nächsten kommt
- Auf der Common-Lisp-Seite ist die Game Engine Trial ein Beispiel dafür, dass viele Teile in Common Lisp implementiert werden, statt C-Bibliotheken nur zu umschließen
- Projekte wie Pre-Scheme wecken die Hoffnung, dass eines Tages auch Schichten unterhalb GC-verwalteter Runtimes in Lisp implementiert werden können
- Pre-Scheme wurde in Scheme 48 entwickelt und erfolgreich genutzt
- Dank eines NLnet grant ist eine moderne Wiederbelebung zu erwarten
Mehr vom Stack in Lisp bauen
- Die Richtung geht eher zum Kuchen
- Es braucht mehr Projekte, die die Grenzen dessen, was Lisp leisten kann, weiter verschieben
- Das Interessanteste am Lisp Game Jam sind weniger die Spiele selbst als die kleinen Fortschritte dabei, ein Stück Kuchen vom alten, trockenen C zurückzuerobern
- In der Guile-Spieleentwicklung soll das Chickadee-Projekt die Grenzen weiter verschieben
- Das Fazit lautet nicht: in Rust neu schreiben, sondern in Lisp neu schreiben
1 Kommentare
Hacker-News-Kommentare
Es war umso erfreulicher, weil man heute kaum noch Texte sieht, die Software-Ansätze objektiv vergleichen
Selbst wenn man nach solchen Texten sucht, kommen die Suchergebnisse heute oft nicht über SEO-Spam hinaus
Janet scheint für Spiele gemacht zu sein, und es wirkt, als hätte es überraschend viele „Batteries included“-Elemente wie Webserver oder Grafik, daher war ich etwas überrascht, keine Spiele zu sehen, die Janet verwenden
Im Bereich Lisp und Games ist es meiner Meinung nach eine Sprache, die man sich einmal ansehen sollte
Schön, dass s7 Aufmerksamkeit bekommt
Ich habe es in Scheme for Max als Scheme verwendet, einer Open-Source-Erweiterung, die einen Scheme-Interpreter in die Computer-Musik-Umgebung Max/MSP einbettet; es liegt irgendwo zwischen Guile, Clojure und Common Lisp, ist dabei aber sehr klein und leicht einzubetten
Gut ist auch, dass es unter einer deutlich großzügigeren BSD-Lizenz steht als Guile
Wenn man Common-Lisp-Makros mit First-Class-Environments mag, stehen die Chancen gut, dass einem auch s7 gefällt
Auch in WASM ließ es sich sehr einfach nutzen, und ich verwende es so in einem Musikbildungsprojekt
Es war auch nicht schwer, generische Funktionen zu bauen, die aus Scheme heraus JS-Funktionen aufrufen und umgekehrt aus JS heraus Scheme aufrufen, sodass der gesamte Ablauf reibungslos war
Wir haben s7 und SQLite erfolgreich als Engine ohne Grafik in nativen Apps für iOS und Android eingebettet
Es war sehr schnell, hatte ein gutes FFI, war stabil und klein, und wir profitierten stark von gemeinsam genutztem Code zwischen mobilen Apps, extrem schnellen Unit-Tests und einer sauberen Toolchain
Am Ende sind wir auf Fennel umgestiegen, ein für Mobile praktischeres Lisp
Während wir fast das einzige Team waren, das s7 auf Mobile nutzte, war Lua als Erweiterungssprache auf Mobile deutlich verbreiteter, und auch der Stand der r7rs-Scheme-Kompatibilität spielte eine Rolle
Da wir für die Desktop-Entwicklung Guile nutzten und für die Auslieferung s7, stolperten wir oft über subtile Inkompatibilitäten, etwa die Auswertungsreihenfolge von Parametern
Sowohl s7 als auch Fennel haben hervorragende Projekte und Communities
Ich empfehle, sich besonders das Spritely Institute anzusehen, einschließlich des Blogs
Ich will nicht spoilern, was sie dort machen, aber es ist ein Thema und eine Institution, in die man sich gut vertiefen kann
Allein mit dem Lesen des Blogs, verwandter Links und Projekte habe ich über 10 Stunden verbracht
https://spritely.institute/archive/
Die Zusammenfassung „Wir leben nicht in der Welt der Lisp-Maschinen, sondern in der Welt eines glorifizierten PDP-11“ ist eindrucksvoll
Ich frage mich allerdings, ob es auch „Icing“ gibt, das man zusammen mit SDL verwenden kann
In dem Sinne, dass es von-Neumann-Maschinen mit ungetaggtem Speicher sind, ähneln sie sich zwar, aber etwa ab Mitte der 80er, als 32-Bit-Mikroprozessoren allgemein wurden und sich die Lisp-Compilertechnik daran anpasste, begannen traditionelle CPUs, Lisp-Maschinen zu überholen
Auch der Punkt „ungetaggter Speicher“ muss sich angesichts von Strömungen wie CHERI nicht unbedingt dauerhaft halten, und es gibt Spielraum für eine größere Rückkehr von LispM-artigen Architekturen
Ein paar Jahre lang hatte ich eine PDP-11/45 im Wohnzimmer stehen; später tauschte ich sie, um Platz zu sparen, gegen mehrere H-11 LSI-11/2 mit je zwei 8-Zoll-Diskettenlaufwerken
Die PDP-11 war nicht nur so etwas wie das Protoplasma von Unix, sondern auch konzeptionell gut entworfen
So wie wir Routinen bevorzugen, die „auf einen Bildschirm passen“, führte der kleine direkte Adressraum der PDP-11 zu nicht zu großen Modulen und förderte Modularität
Ist das heute unzureichend? Natürlich, besonders bei Big Data ist es schmerzhaft
Trotzdem gibt es konzeptionelle Gründe dafür, dass die PDP-11 erfolgreich war und bis heute Spuren hinterlassen hat
Es gibt keinen anderen Weg, C zu reparieren, und es gibt zu viel Code, der nie neu geschrieben werden wird
Lisp arbeitet hauptsächlich mit Listen, und Listen können Zeigern quer durch den Speicher folgen
Bei früheren CPUs war das kein Problem, weil Speicherzugriffe im Allgemeinen ungefähr die gleiche Random-Access-Zeit hatten, aber moderne CPUs können Speicherzeigern nicht mit derselben Geschwindigkeit folgen und müssen für Performance Lokalitätsregeln einhalten
Deshalb müssen Algorithmen, die etwas wie C- oder Fortran-Arrays verwenden, zwangsläufig immer schneller sein als Lisp-Listen-basierte Versionen
Die jüngsten Fortschritte bei Guile Scheme machen Hoffnung
Anders als beim letzten Mal, als ich es mir angesehen habe, ist es von einer interpretierten Sprache zu einer Sprache mit einem ordentlichen Compiler geworden, und mit Hoot kann es nun auch nach WASM kompilieren
Mit Clojure, uLisp und Common Lisp bin ich vertraut, aber Guile Scheme fühlt sich an, als hätte es viel von Common Lisps Ballast abgeworfen, und besonders wenn Guix und Shepherd sich durchsetzen, hätte ich gern ein kompiliertes Lisp zur Hand
Ich frage mich, ob es neben Little Lisper und SICP gute Materialien gibt, um Guile Scheme effektiv zu lernen
Ich überlege, Guile für etwas zu verwenden, das ich demnächst bauen will, daher fand ich den Text interessant
Auch die Arbeit auf der WASM-Seite scheint gelungen zu sein
https://wingolog.org/archives/2024/05/16/on-hoot-on-boot
Kürzlich habe ich mit Clojure einen Prototyp für einen 3D-Bosskampf gebaut: https://prototype-game.pages.dev
Früher habe ich jede Form von Webentwicklung gehasst, aber ClojureScript hat sie tatsächlich angenehm gemacht, und ich wünschte, es würde breiter genutzt
Ich werde es später noch einmal versuchen, wenn ich am PC sitze
Ich frage mich, welche Bibliotheken du verwendest
Das war eines der Spiele, die ich als Kind wirklich geliebt habe
https://www.youtube.com/watch?v=dcJFldES9dg
Ich hatte zwar darum gebeten, es in die Tabelle aufzunehmen, aber vielleicht ist das inzwischen schon eine alte Nachricht.
Referenz:
https://lispy-gopher-show.itch.io/logos-lisp-legend/devlog/7...
https://itch.io/post/10013482
Obwohl es https://ianthehenry.com/posts/janet-game/ gibt, fehlt Janet.
Das Copyright-Jahr des Artikels ist mit 1899–1907 angegeben, aber schade ist, dass er nicht wirklich vintage wirkt.
Für Scripting bin ich wieder zu Fennel zurückgekehrt, weil man viel mehr Lua-Bibliotheken direkt verwenden kann und es fast überall problemlos läuft, sogar in a-Shell auf dem iPad.
In diesem Thread gab es nützliche Referenzen, und vielleicht versuche ich es später noch einmal: https://janet.zulipchat.com/#narrow/stream/409517-help/topic...
Obwohl es bereits einen bewährten Produktionspfad gab, habe ich gerade noch rechtzeitig etwas Spielbares gebaut, wobei „spielbar“ schon recht großzügig formuliert ist.
Ich bin sehr neugierig, welche Art von Spiel mit Emacs Lisp gebaut wurde.
Wenn man an Spieleprogrammierung denkt, ist das nicht gerade die naheliegendste Wahl.
Dunnet wurde ursprünglich 1982 von Ron Schnell als Maclisp-Programm für TOPS-20 geschrieben.
1992 portierte er es nach Emacs Lisp, beließ es aber nicht bei einer einfachen Portierung, sondern fügte neue Räume, Gegenstände und Rätsel hinzu und entfernte MIT-zentrierte Inhalte.
Zum Beispiel hieß der „Endgame“-Computer gegen Ende des Spiels ursprünglich MIT-SALLY, befand sich am MIT und war über Chaosnet erreichbar; in der GNU-Emacs-Version wurden solche veralteten MIT-Bezüge jedoch entfernt.
Stattdessen sind bewusst Dinge wie VAX 11/780 enthalten, die breiter wiedererkennbar, aber ebenfalls zeittypisch sind.
https://en.wikipedia.org/wiki/Dunnet_(video_game)
Das Original ist hier: https://github.com/Quogic/DunnetPredecessor/blob/master/foo....
https://lcolonq.itch.io/slgj2024-game-boy-gizmo
https://asquared31415.itch.io/disassembly
https://grindingstone.itch.io/pendulum