Dies ist ein Update zu solid-ml, einem Experiment um herauszufinden, ob die Philosophie von SolidJS (feingranulare Reaktivität, ein "No-Virtual-DOM"-Ansatz und performantes Hydration) von KI-Agenten getreu nach OCaml portiert werden kann.
Das Projekt hat sich von einem Proof-of-Concept zu einem (in Bezug auf die gemeinsame Komponenten-Entwicklungserfahrung) attraktiven, aber nicht erprobten Framework entwickelt. Mit über 200 bestandenen Tests über seine Kernpakete hinaus, behauptet solid-ml jetzt Server-Side Rendering (SSR), client-seitige Hydration und SSR-aware Routing zu unterstützen. Hier ist die Geschichte, wie es gebaut wird und warum ich Ideen wie diese wertvoll finde, auch wenn das Ergebnis am Ende sein kann, dass LLMs nicht geeignet sind, Ökosystem-Lücken auf diese Weise zu füllen. Ich habe trotzdem die Hoffnung, dass kommende neue Modelle dabei helfen können.
Der Workflow: Mensch als "Aufseher"
Das ungewöhnlichste Aspect dieses Projekts ist der Entwicklungsprozess: Ich habe den OCaml-Code nicht selbst geschrieben. Stattdessen war ich der "Aufseher" für zwei KI-Agenten mit hoher Reasoning-Fähigkeit: Claude 4.5 Opus und GPT-5.2.
Meine Rolle ist es, die Architektur zu managen und die Agenten auf Kurs zu halten. Wenn die Modelle "verrückt klingende" Ideen vorschlagen, halte ich sie an und bitte sie, einen Schritt zurückzutreten und ihre Entscheidungen zu erklären. Manchmal weise ich sie auf den Kern-Blueprint zurück: "Was würde SolidJS tun?", oder helfe ihnen, die einfachste oder robusteste Implementierung zu wählen, je nachdem, wie die Trade-offs aussehen.
Ich habe einen deutlichen Unterschied in den "Persönlichkeiten" der Modelle bemerkt:
- Opus 4.5 war der Motor der frühen Phase. Er fühl sich flinker an und war exzellent darin, den initialen Schwung zu erzeugen.
- GPT-5.2 ist ein langsameres, gründlicheres Modell. Es behauptet nicht enthusiastisch, alles sei "produktionsbereit", wenn die Hälfte der Implementation fehlt. Stattdessen ist es auf eine hilfreiche Weise pessimistisch, weist auf Mängel hin und arbeitet durch komplexe Logik wie Hydration-Marker und reaktives Dependency-Tracking.
Warum OCaml? Der Vertrauensfaktor und SSR
Du könntest fragen, warum wir einen SolidJS-Port in OCaml brauchen, wenn Frameworks wie Bonsai, Eliom oder ReasonReact bereits existieren. Und dann gibt es noch Elm (nicht OCaml, aber geistig verwandt). Ich denke nicht unbedingt, dass wir ein "SolidJS aber in OCaml" brauchen. Allerdings wollte ich sehen, was heute mit KI-Agenten möglich ist. Die Architektureines Frameworks beinhaltet tausende subtile Trade-offs. Indem wir einer "getreuen Port"-Strategie von SolidJS folgen, verlassen wir uns auf die Entscheidungen, die in einer Architektur kodifiziert sind, die sich bereits in der JavaScript-Welt bewährt hat.
Über die API hinaus gewinnen wir einige spezifische "unfaire Vorteile" durch OCaml als Sprache:
- Der Static-Binary-Vorteil: Auf der Server-Seite erhalten wir native OCaml-Performance, grob in derselben Liga wie Go. Wir können die gesamte Anwendung in eine einzelne, leichte, statisch gelinkte Binary kompilieren und auf jeden Linux-VPS deployen, ohne uns über Node.js-Runtimes oder schwere Container-Images sorgen zu müssen.
- Multicore: Seit OCaml 5 haben wir native Multicore-Unterstützung.
solid-mlist mit diesem im Hinterkopf entworfen und verwendet Domain-local Storage, um sicherzustellen, dass der reaktive Graph während der parallelen Ausführung auf dem Server thread-sicher bleibt.
Der Trade-off: Client-Performance vs. Engineering-Sicherheit
Es ist wichtig, bezüglich der Performance auf dem Boden der Tatsachen zu bleiben. Während die SSR-Seite schnell sein wird, werden wir auf der Client-Seite einen Performance-Hit einstecken. OCaml, das zu JavaScript kompiliert wird (via Melange), ist im Allgemeinen nicht so performant wie hand-optimiertes JS.
Das wird jedoch durch einen massiven Engineering-Vorteil ausgeglichen: Geteilte Logik.
Indem wir OCaml sowohl für das Backend als auch für das Frontend verwenden, können wir das gesamte Datenmodell und die gesamte Validierungslogik teilen. Wenn du einen Kern-Typ auf dem Backend refactorst, wird dir der Compiler sofort jeden einzelnen Bruch in deinen UI-Komponenten anzeigen. Client-seitige Validierung und server-seitige Validierung können nicht aus dem Takt geraten. Diese End-to-End-Sicherheit macht Anwendungen viel einfacher zu warten und über Zeit zu warten. Das ist ein nicht zu vernachlässigender Vorteil, wenn man es mit komplexen Systemen zu tun hat.
Die geheime Zutat: MLX und Melange
Die Developer Experience (DX) von solid-ml wird von zwei Libraries/Tools geprägt, die die Lücke zwischen OCaml und dem modernen Web überbrücken:
- MLX ist eine JSX-ähnliche Syntax für OCaml, die meiner Meinung nach besser anfühlt als TSX. Sie ist sicher und lesbar und sorgt dafür, dass HTML-Open/Close-Tags übereinstimmen. Sie trifft den Sweet Spot: Sie ist ergonomischer als voll-typisierte Lösungen wie TyXML, aber viel sicherer als einfache String-Templates. Es ist genau das, was ich will, wenn ich HTML-Templates in OCaml schreibe.
- Melange übernimmt die client-seitige Kompilierung. Das JS-Interop ist glatt, und die Möglichkeit zur Separate-File-Kompilierung ist ein großer praktischer Vorteil. Es ermöglicht uns, das Frontend-Bundle organisiert zu halten und bei Bedarf in das weitergefächerte JS-Ökosystem integriert.
Der "Uncanny Valley" von KI-gesteuertem Code
Wir befinden uns derzeit in einer Art "Uncanny Valley". Die Tests bestehen, die Demos funktionieren. Es gibt eine Multi-Page-App, die Posts lädt und anzeigt, mit mehreren Routes zum Erkunden. Aber dies bleibt ein LLM-gesteuertes Projekt. Es besteht immer die Gefahr, dass der Code zu einem "Dschungel" von subtilen Bugs werden könnte, die schwer zu beheben sind, sobald die Komplexität einen bestimmten Schwellenwert erreicht.
Das ist eine Realität für jedes Projekt dieses Ausmaßes, ob von Menschen oder KI geleitet. Projekte dürfen scheitern. Wenn sich das als unlösbares Chaos entpuppt, habe ich immerhin eine gute Geschichte für den Versuch, und es gibt keine Schande, zurück zu SolidJS zu gehen, oder einige Experimente mit Bonsai oder anderen vor-der-KI-Ära OCaml-Frontend-Frameworks zu machen.
Der nächste Schritt: Ich stehe kurz davor, einen Branch zu mergen, der ordentliche MLX-Templates und das Teilen von Komponenten zwischen SSR und dem Client einführt. Ich werde die Agenten das gesamte Frontend einer bestehenden App an der ich gerade entwickle mit dieser neuen API neu schreiben lassen. Das wird ein realistischer Test. Wenn es erfolgreich ist, könnte ich solid-ml vielleicht überall verwenden, bis etwas Besseres kommt. Aber wer weiß... sicher nicht ich, an diesem Punkt.
Wie solid-ml gebaut wurde: Eine Timeline
Das Projekt startete am 5. Januar mit einem grundlegenden reaktiven Kern. Innerhalb von 24 Stunden wurde klar, dass die erste Implementierung nicht eng genug an der SolidJS-Architektur angelehnt war: die bidirektionalen Signal<->Computation-Links und die State Machine, die SolidJS funktionieren lassen, fehlten. Tag 2 sah ein komplettes Rewrite durch den Agenten, gefolgt sofort von der Extraktion eines gemeinsamen solid-ml-core Pakets, um die 450+ Zeilen duplizierten Codes zwischen Server- und Browser-Runtimes zu eliminieren.
Ab Tag 3 verlagerte sich der Fokus auf Hydration und Routing. Der Agent implementierte marker-basierte Text-Node-Adoption (kritisch für SSR-Hydration) und baute einen Browser-Router mit History-API-Unterstützung. Auf meine Anweisungen implementierte der KI-Agent das beliebte js-framework-benchmark. Ich wollte verstehen, wie schlimm der Performance-Hit gegen SolidJS aussieht. Im Ergebnis hat der KI-Agent Performance-Optimierungen vorgenommen: Der udomdiff-Reconciliation-Algorithmus wurde implementiert, um SolidJS zu entsprechen. An Tag 6 wurde die Bundle-Größe um 66% reduziert (235KB → 80KB).
Tag 7 brachte eine große architektonische Verschiebung: Die SSR- und Browser-HTML-Module hatten inkompatible APIs, was wahre Isomorphie (Code-Sharing vom Komponenten zwischen SSR Server und Browser Client) unmöglich machte. Ein gemeinsames Html_intf.S-Interface wurde entworfen, und beide Runtimes wurden angepasst, um es zu erfüllen. Am selben Tag wurden inline onclick-Handler durch globales Event Delegation ersetzt: sowohl eine Security-Fix als auch eine Skalierbarkeitsverbesserung.
Async-Data-Handling war der Fokus an Tag 9, mit Async/Action/Store-Modulen. Ein kritischer Bug wurde gefunden: Das reaktive Dependency-Tracking für Promise-basierte Berechnungen war fehlerhaft und musste behoben werden. Die Tage 11-16 sahen den Template-Compiler entstehen: MLX-Integration, ein PPX-System für Compile-Time-Template-Verarbeitung und sorgfältige Handhabung von Whitespace-Normalisierung (Formatierungs-Whitespace störte die Template-Kompilierung).
Tage (16-17): Die vollständige SSR-App wurde zu MLX-Templates migriert, Hilfsfunktionen wurden hinzugefügt und striktes SSR-Rendering durchgesetzt.
Tag 18: Debugging der Beispiele.
| Tage | Fokus | Key Changes |
|---|---|---|
| 1-2 | Reactive Core | Komplettes Rewrite (SolidJS-Patterns), gemeinsamer Functor extrahiert (450 Zeilen entfernt) |
| 3-6 | SSR & Performance | Hydration-Marker, Browser-Router, udomdiff, 66% Bundle-Reduktion |
| 7-8 | Unifikation | Vereinigte SSR/Browser APIs unter gemeinsamen Interface, Event Delegation (Security) |
| 9-10 | Async Data | Async/Action/Store-Module, reaktives Dependency-Tracking behoben |
| 11-16 | Template Compiler | MLX-Integration, PPX-System, Whitespace-Normalisierung |
| 17 | MLX-Migration | Vollständige App zu MLX, scoped Helpers, striktes SSR |
| 18 | Debugging der Beispiele | Komplexe Beispiele in Docker Containern |
Das Muster, das sich herauskristallisierte: Frühe Arbeit wurde häufig durch treuere Implementierungen der SolidJS-Architektur ersetzt. Jedes Major-Feature (Routing, SSR, Templates) benötigte mehrere Iterationen, als versteckte Komplexität ans Tageslicht kam.
Ergebnis: 18 Tage, 125 Commits, 200+ bestandene Tests und ein Framework, von dem wir noch nicht wissen, ob und wie gut es SSR, Client-seitige Hydration und SSR-aware Routing in der Praxis wirklich unterstützt.
solid-ml ist ein SolidJS-inspiriertes Framework für OCaml. Gebaut mit Melange und MLX.
