Ein Go-Release bringt Go-Entwickler dazu, darüber zu streiten, was Go überhaupt ist, und ich gebe zu: Von unserer Seite des Zauns aus genieße ich den Anblick. Go 1.27 bringt generische Methoden, eine neu geschriebene JSON-Engine, die unter die alte API geschoben wurde, UUIDs in der Standardbibliothek, experimentelles portables SIMD, Post-Quanten-Signaturen mit ML-DSA, günstigere Allokationspfade für Objekte unter 80 Bytes und eine Laufzeiterkennung für Goroutinen, die nie wieder aufwachen werden. Prompt kommt die Angst hoch: Hat die Sprache, die ihre Marke auf dem Neinsagen aufgebaut hat, endlich angefangen, Ja zu sagen? Ich glaube, diese Angst zielt auf das falsche Ziel. Einfachheit war nie eine Eigenschaft des Datenblatts. Sie lebt in dem Diff, das dein Kollege an einem Dienstagnachmittag reviewt, und nach diesem Maßstab kann eine Sprache enorm komplizierte Interna entwickeln und trotzdem dort einfach bleiben, wo es zählt. PHP hat das vor Jahren begriffen, größtenteils aus Versehen, und Go 1.27 liest sich für mich wie das offizielle Eingeständnis, dass wir da etwas richtig gemacht haben.
Schau dir zuerst die JSON-Änderung an, denn sie ist der PHP-artigste Schachzug im ganzen Release. Go hat jetzt encoding/json/v2 mit strengeren Defaults und mehr Konfiguration, aber das alte encoding/json funktioniert weiter, weil es inzwischen auf der neuen Maschinerie läuft, mit einem Opt-out, falls der Übergang etwas kaputt macht. Alter Import, neue Engine. Genau das haben wir schon durchlebt. PHP 7 hat die Eingeweide der Zend Engine ersetzt, zvals und Hashtables umgebaut, und die meisten Codebasen haben das Upgrade erledigt, indem sie ein Docker-Tag geändert haben. PHP 8.0 hat einen JIT an opcache geschraubt, und deine Controller haben davon weder etwas gewusst noch hat es sie interessiert. Niemand hat sich auf einer Konferenz hingestellt und PHP Verrat am Minimalismus vorgeworfen, weil niemand die Engine von PHP jemals für ein minimalistisches Artefakt gehalten hat. Der Deal war immer, dass der Kern die hässliche Arbeit macht, damit Userland langweilig bleibt, und langweiliges Userland ist der ganze Sinn der Übung.
Generische Methoden sind der härtere Fall, und ich will fair zu den Leuten sein, die nervös werden. Eine Methode, die ihre eigenen Typparameter einführen kann, unabhängig vom Receiver, ist kein Engine-Klempnerkram. Sie taucht im Code-Review auf. Sie lässt Result-Typen mit einer Map, die in ein anders parametrisiertes Result transformiert, wie normalen Code wirken, und sobald sich das normal anfühlt, folgen fluente typisierte Pipelines, dann Builder-APIs, dann Bibliotheksdesigns, importiert aus Rust und Kotlin. Das stärkste Argument gegen ausdrucksstarke Features war nie, dass sie nutzlos wären. Es lautet, dass sie verändern, wonach deine Teamkollegen standardmäßig greifen, und der Default-Stil eines Teams ist viel schwerer zu reviewen als jede einzelne clevere Funktion. Dieses Argument verdient Respekt. Ich habe eine Laravel-Codebasis erlebt, in der ein einziger enthusiastischer Macro-Autor still und leise neu definiert hat, was idiomatisch für alle anderen im Team bedeutet.
Und trotzdem lande ich auf der Seite der Neuerungen, teils wegen der Art, wie Go sie eingezäunt hat. Interface-Methoden dürfen in 1.27 keine eigenen Typparameter deklarieren, und generische Methoden können keine Interface-Verträge erfüllen, was heißt, dass die neue Ausdruckskraft zwar existiert, sich aber nicht über den wichtigsten Abstraktionskanal der Sprache ausbreiten kann. Das ist Rationierung, und Rationierung funktioniert. PHP fährt täglich eine Version desselben Experiments: Wir haben Generics in der Praxis über Docblock-Annotationen, durchgesetzt von PHPStan oder Psalm, mit null Unterstützung zur Laufzeit. Das ist unelegant, eher ein Waffenstillstand als ein Design, aber der prophezeite Kollaps in Abstraktionssuppe ist nie eingetreten. Teams, die typisierte Collections wollten, haben sie bekommen. Teams, die das nicht wollten, haben weiter Arrays geschrieben. Das Tooling hat die Komplexität getragen, damit die Engine es nicht musste, und das ist einfach eine weitere Antwort auf dieselbe Frage, wer am Ende die Last trägt.
Die UUID-Geschichte in der Stdlib ist der Punkt, an dem die beiden Ökosysteme wirklich auseinandergehen, und da bin ich mir weniger sicher, wer recht hat. Go hat UUIDs in die Standardbibliothek geholt und musste database/sql dann eine Sonderbehandlung für den neuen Typ verpassen, damit er sich bei Datenbankkonvertierungen anständig benimmt, eine Warze, die dem Ethos der kleinen Interfaces zuwiderläuft. PHP ist den komplett anderen Weg gegangen: Der Kern hat nie UUIDs ausgeliefert, und ramsey/uuid plus symfony/uid wurden über Composer zu De-facto-Standards. Unser Ansatz hält den Kern sauber und drückt die Entscheidung jedem einzelnen Projekt aufs Auge. Ihr Ansatz segnet eine Implementierung ab und akzeptiert dafür einen architektonischen Fleck, um sie ergonomisch zu machen. Ich habe composer require ramsey/uuid so oft getippt, dass meine Finger das inzwischen unbeaufsichtigt erledigen, und ich kann dir trotzdem nicht sagen, ob das Freiheit ist oder nur eine Steuer, die ich nicht mehr bemerke.
Wogegen ich mich wehren würde, ist das Dammbruch-Framing, also die Idee, dass eine Sprache, die lernt, Komplexität zu absorbieren, so lange weiter absorbiert, bis sie zu dem Ding wird, vor dem sie geflohen ist. Anhäufung ist real, klar. Aber die Leitplanke ist nicht Verweigerung, sondern eine lesbare Regel dafür, wo Komplexität wohnen darf. PHPs implizite Regel hat sich besser gehalten, als wir ihr zugestehen: Gib unterhalb der Oberfläche großzügig aus, in opcache, im JIT, beim Preloading und in der FFI-Schicht, und sei geizig mit Syntax, die verändert, wie durchschnittlicher Code aussieht. Enums, readonly Properties, die First-Class-Callable-Syntax, all das hat eine hohe Hürde genommen und sich seinen Platz verdient. Der RFC-Prozess ist langsam und gelegentlich zum Verrücktwerden, und er ist zugleich der Grund, warum sich PHP-8.4-Code für jemanden, der das Ökosystem 2016 verlassen hat, immer noch wie PHP liest.
Und jetzt zu dem, was ich dich wirklich fragen will, denn die Go-Debatte ist eine Generalprobe für eine, die wir ständig vertagen. Wenn morgen ein RFC landen würde, der echte reifizierte Generics in PHP anbietet, von der Engine durchgesetzt, ohne Docblocks, zum Preis ernsthafter interner Komplexität und der fast sicheren Folge, dass jede größere Bibliothek innerhalb von zwei Jahren typisierte Builder- und Pipeline-APIs austreibt: Würdest du mit Ja stimmen? Oder ist der aktuelle Waffenstillstand, bei dem statische Analyse den Job der Generics macht, während sich die Runtime raushält, insgeheim die beste Version von Komplexitätsplatzierung, die wir absichtlich hätten entwerfen können? Ich schwanke wöchentlich. Sag mir, wo du landest, und sag mir, wie deine Codebasis drei Jahre nach der einen oder der anderen Antwort aussehen würde.
Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.