- Clojure 1.12.0 wird als letzte auf Java 8 ausgerichtete Version angekündigt, die weiterhin Java-8-Bytecode beibehält, bevor in späteren Releases die minimale Java-Kompatibilität und die Bytecode-Basis auf ein aktuelles Java-LTS verschoben werden
- In Umgebungen mit virtuellen Threads von JDK 21 verwenden
lazy-sequnddelayLocks stattsynchronized, wodurch Situationen reduziert werden, in denen blockierendes I/O reale Threads pinnt - Im REPL können mit
add-lib,add-libsundsync-depsBibliotheken ohne Neustart der JVM hinzugefügt werden; diese Funktion ist jedoch auf interaktive Nutzung während der Entwicklung beschränkt - Die Java-Interoperabilität wurde erweitert: Method Values,
:param-tags, Syntax für Array-Klassen, Umwandlung funktionaler Interfaces,Suppliersowie Funktionen zur Verarbeitung von JavaStreamwurden hinzugefügt - Bei Performance und Kompatibilität enthält die Version einen
PersistentVector-Spliterator, effizienteresdrop/Partitioning, eine strengere Var-Interning-Policy, die Behebung von CVE-2024-22871 sowie eine Bereinigung von Java-Serialisierungskennungen
Java-8-Kompatibilität, Sicherheit und Serialisierung bereinigt
- Informationen zu Download und Nutzung von Clojure 1.12.0 finden sich auf der Seite Downloads
- Die Java-8-Basis bleibt auch in diesem Release erhalten
- Clojure 1.12 erzeugt wie Clojure 1.10 und 1.11 Java-8-Bytecode
- Spätere Releases sollen Bytecode und minimale Java-Kompatibilität auf ein aktuelleres Java-LTS-Release verschieben
- Das Problem mit Pinning virtueller Threads in JDK 21 wird abgemildert
- Vor 1.12 führten
lazy-sequnddelayBenutzercode innerhalb einessynchronized-Blocks aus, um sicherzustellen, dass er nur einmal ausgeführt wird - Unter JDK 21 nimmt
synchronizednoch nicht an kooperativem Blocking teil; wenn dieser Code blockierendes I/O ausführt, kann dadurch ein realer Thread gepinnt werden - Bei Verwendung von
-Djdk.tracePinnedThreads=fullkann JDK 21 für diese Situation eine Warnung ausgeben - In 1.12 verwenden
lazy-sequnddelaystattsynchronized-Blöcken Locks
- Vor 1.12 führten
- Als Sicherheitsfix wurde CVE-2024-22871 berücksichtigt; die zugehörige Advisory findet sich unter GHSA-vr64-r9qj-h27f
- Die serialVersionUID von Klassen im Zusammenhang mit Java-Serialisierung wird explizit gesetzt
- Clojure-Datentypen implementieren seit Clojure 1.0 das Java-Serialisierungsinterface
- Java-Serialisierung funktioniert bei der Deserialisierung nur, wenn die aus Klassenname, Typhierarchie und serialisierten Feldern erzeugte Kennung übereinstimmt
- Clojure garantiert keine Serialisierungskonsistenz zwischen Versionen, führt aber Änderungen ein, die künftig mehr Kontrolle ermöglichen, um Kompatibilität nicht stärker als nötig zu brechen
- Auch Abhängigkeiten wurden aktualisiert
spec.alphawurde auf 0.5.238 aktualisiertcore.specs.alphawurde auf 0.4.74 aktualisiert
Funktionen für Bibliotheken und Tools im REPL
- Während der Entwicklung gibt es Situationen, in denen Bibliotheken hinzugefügt werden müssen, ohne die JVM neu zu starten
- experimentelle Auswertung
- Hinzufügen einer bekannten Abhängigkeit zu einem Projekt
- Hinzufügen einer Bibliothek für eine bestimmte Aufgabe
- Clojure 1.12 stellt neue Funktionen bereit, um Bibliotheken hinzuzufügen, ohne den REPL-Zustand zu verlieren
add-lib: lädt eine nicht im Classpath vorhandene Lib herunter und fügt sie dem Classloader hinzu- Libs, die bereits im Classpath vorhanden sind, werden nicht aktualisiert
- Wenn keine Koordinaten angegeben sind, wird die neueste Maven-Version verwendet oder, falls der Name eines Git-Repositorys abgeleitet werden kann, die neueste Git-Version bzw. ein Tag
add-libs: löst mehrere neue Bibliotheken und Versionen gemeinsam aufsync-deps: ruftadd-libsfür Libs auf, die indeps.ednstehen, aber noch nicht im Classpath sind
- Diese Funktionen sind nur für die REPL-Nutzung während der Entwicklung gedacht
- Der richtige Weg zum Bauen und Pflegen von Produktionscode bleibt weiterhin die Nutzung von
deps.edn - Alle drei Funktionen prüfen, ob
*repl*auf true gebunden ist clojure.main/replbindet dieses Flag automatisch- Im
clojure.main-REPL werden die neuen Funktionen automatisch in den Namespaceuserreferenziert - In anderen REPLs kann
(require '[clojure.repl.deps :refer :all])erforderlich sein
- Der richtige Weg zum Bauen und Pflegen von Produktionscode bleibt weiterhin die Nutzung von
- Auflösung und Download von Bibliotheken übernimmt tools.deps
- Damit
tools.depsund seine Abhängigkeiten während der Entwicklung nicht in den Projekt-Classpath gelangen, wurde außerdem eine neue API hinzugefügt, die die Funktionen über die Clojure CLI in einem separaten Prozess aufruft
- Damit
clojure.tools.deps.interop/invoke-toolruft Tool-Funktionen in einem separaten Prozess auf- Der Classpath des Tools wird in
deps.edndefiniert - Tool-Abhängigkeiten müssen nicht zum Projekt-Classpath hinzugefügt werden
- Die
add-lib-Funktion wurde mitinvoke-toolumgesetzt und kann auch genutzt werden, um eigene Tools interaktiv zu bauen oder aufzurufen - Das Protokoll zur Funktionsausführung ist in der CLI reference beschrieben
- Der Classpath des Tools wird in
API zum Ausführen externer Prozesse
- Zusätzlich zum bestehenden Namespace
clojure.java.shellgibt es nun einen Weg, der die neueren Java-APIs für Prozessinformationen, Steuerung und I/O-Umleitung nutzt - Clojure 1.12 fügt den neuen Namespace
clojure.java.processhinzu- Er nutzt die neueren prozessbezogenen Java-APIs
- Er ist benutzerfreundlicher gestaltet als der bisherige Ansatz
- Die wichtigsten Funktionen sind:
Erweiterte Java-Interoperabilität
- Method Values wurden hinzugefügt, sodass Java-Methoden direkter in Higher-Order Functions verwendet werden können
- Bisher mussten Java-Methoden manuell in Funktionen verpackt werden, um sie etwa an
mapzu übergeben - Manuelles Wrapping ist umständlich und kann Hinweise zur Unterscheidung von Overloads erfordern oder zusätzliche Reflection bzw. Boxing verursachen
- Nun können qualified methods an Wertpositionen wie normale Funktionen verwendet werden; der Compiler erzeugt automatisch eine Wrapper-Funktion
- Wenn eine qualified method wegen Overloads nicht aufgelöst werden kann, erzeugt der Compiler einen Reflection-Aufruf
- Entwickler können mit
:param-tags-Metadaten die gewünschte einzelne Methodensignatur angeben
- Bisher mussten Java-Methoden manuell in Funktionen verpackt werden, um sie etwa an
- Die Syntax für qualified methods gibt Klasse und Methode explizit an
Classname/method: Clojure-Funktionswert, der eine statische Methode aufruftClassname/.method: Clojure-Funktionswert, der eine Instanzmethode aufruftClassname/new: Clojure-Funktionswert, der einen Konstruktor aufruft- Zur Unterscheidung zwischen statischen und Instanzmethoden müssen die Syntaxformen
Classname/methodundClassname/.methodverwendet werden
:param-tags-Metadaten dienen zur Auflösung überladener Methoden- Eine als Wert verwendete qualified method liefert nur Klasse und Methodenname und kann daher überladene Methoden nicht auflösen
:param-tagsist ein Vektor der Form[tag …], wobei jedes Tag einem Parameter der gewünschten Signatur entspricht- Für Parameter mit nicht überladenem Typ kann der Platzhalter
_verwendet werden - Wenn
:param-tagsangegeben wird, muss der Compiler die Methode zur Compile-Zeit eindeutig auf eine einzelne Methode auflösen können - Die neue Metadaten-Reader-Syntax
^[tag …]fügt einem Member-Symbol:param-tags-Metadaten hinzu
- Syntax für Array-Klassen wurde hinzugefügt
- Clojure unterstützte Klassennamenssymbole als Klassenobjektwerte und Type Hints, bot aber außer Strings keine Syntax für Array-Klassen
- Nun können Array-Klassen über Symbole der Form
ComponentClass/#dimensionsreferenziert werden - Beispiele:
String/1,java.lang.String/1,long/2 - Die Komponentenklasse kann ein voll qualifizierter Klassenname, eine importierte Klasse oder ein Primitive sein
- Die Syntax für Array-Klassen kann sowohl als Type Hint als auch als Wert verwendet werden
- Die Interoperabilität mit Java-Functional Interfaces wurde verbessert
- Java Functional Interfaces sind mit
@FunctionalInterfaceannotiert und haben genau eine Methode - Clojure-Funktionen können an Aufrufe von Java-Methoden übergeben werden, die ein Functional Interface erwarten, sofern die Arity passt
- Der Clojure-Compiler erzeugt einen Lambda-Adapter, der die Clojure-Funktion implizit in das benötigte Functional Interface umwandelt
- Um in Schleifen wiederholte Adapter-Erzeugung zu vermeiden, kann durch einen Hint am Namen einer
let-Binding explizit erzwungen werden
- Java Functional Interfaces sind mit
- Auch die
Supplier-Interoperabilität wurde verbessert- Um eine Methode aufzurufen, die einen wertebereitstellenden
Suppliererwartet, musste bisher ein Adapter mitreifygeschrieben werden - Clojure-Implementierungen von
IDerefwiedelay,futureundatomimplementieren nun direkt dasSupplier-Interface
- Um eine Methode aufzurufen, die einen wertebereitstellenden
Stream-Verarbeitung und bessere Collection-Performance
- Es wurden Funktionen hinzugefügt, um
Stream, die Java-APIs zunehmend zurückgeben, auf Clojure-Art zu konsumieren- Zusammen mit der Unterstützung von Functional Interfaces in Clojure 1.12 werden Stream-Interop-Funktionen bereitgestellt
(stream-seq! stream) ⇒ seq(stream-reduce! f [init-val] stream) ⇒ val(stream-transduce! xf f [init-val] stream) ⇒ val(stream-into! to-coll [xf] stream) ⇒ to-coll- Alle Funktionen sind terminale Stream-Operationen und konsumieren den Stream
PersistentVectorstellt direkt einen Spliterator bereit, der in der Stream-Implementierung von Java-Collections verwendet wird- Ein Spliterator ist ein Iterator, der für schnellere parallele Traversierung aufgeteilt werden kann
- Der neue Custom-Spliterator von
PersistentVectorunterstützt Parallelität und verbessert die Performance deutlich
- Die Effizienz von
drop,nthrest,nthnextund Partitioning wurde verbessert- CLJ-2713 fügt das interne Interface
IDrophinzu, das anzeigt, dass eine Collection effizienter droppen kann als durch sequenzielles Traversieren - Dieses Interface ist für Persistent Collections und algorithmische Collections wie
rangeundrepeatimplementiert - Die neuen Funktionen
partitionv,partitionv-allundsplitv-atsind effizienter als ihre bestehenden Gegenstücke und erzeugen Vektor-Partitionen statt realisierter Seq-Partitionen
- CLJ-2713 fügt das interne Interface
Strengere Var-Interning-Policy
- Das Interning einer Var in einem Namespace erstellt, anders als Aliasing, eine stabile Referenz, sodass alle Referenzen dasselbe Objekt erhalten
- Bisher gab es einige Fälle, in denen eine interned Var ersetzt werden konnte; in 1.12.0-alpha1 wurde die Policy verschärft
- Wenn eine solche Situation auftritt, erscheint eine Warnung der Form
"REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first"
- Wenn eine solche Situation auftritt, erscheint eine Warnung der Form
- Diese Policy behandelt die zugrunde liegende Ursache eines Problems, das mit neuen Funktionen in
clojure.corein Clojure 1.11.0 sichtbar wurde, insbesondere mitabs
Vollständige Änderungsliste
- Die vollständige Änderungsliste für Clojure 1.12.0 findet sich im offiziellen Changelog
1 Kommentare
Meinungen auf Hacker News
Wirklich ein großes Release mit vielen tollen neuen Funktionen.
Persönlich gefällt mir add-libs am besten. Damit kann man jetzt Single-File-Demos oder Minimalbeispiele zur Reproduktion von Issues erstellen, wodurch die Hürde, kleine ausführbare Code-Snippets zu teilen, deutlich sinkt.
Man kann auch Java-Bibliotheken ohne Java-Boilerplate demonstrieren. Nachdem man im REPL etwas ausprobiert hat, kann man den Code etwa in einen HN-Kommentar einfügen, und jeder kann exakt dasselbe „Setup“ reproduzieren und ausführen. Es ist nicht einmal nötig, ein Repository zu klonen.
Ich dachte, sie würden dieses Release bis zur Clojure/conj 2024 verschieben. Dafür gab es zwar keinen konkreten Grund, aber Clojure 1.10 kam ungefähr zur Clojure/conj 2021 heraus, und die Ankündigung, dass Datomic kostenlos wird, gab es ebenfalls zu Beginn der Clojure/conj 2023.
Trotzdem warte ich noch immer auf spec2. Derzeit umgehe ich die Starrheit von spec mit Malli, aber das ist in Clojure kein First-Class Citizen. Hauptsächlich, weil man Makros nicht prüfen kann; das ist im Design des Clojure-Compilers so vorgesehen. Wenn man Malli-Schemas jedoch als Daten manipuliert, kann man die Idee von schema/select nachahmen.
Durch die Änderung bei funktionalen Interfaces muss man jetzt keine Utility-Makros wie
(defmacro ->Consumer [f] ...)mehr pflegen, sondern kann Funktionen direkt übergeben.Tatsächlich prüft auch Clojure core Makroaufrufe mit spec, sodass man bei falschen Aufrufen manchmal Spuren davon im Stacktrace sieht. Ich frage mich, ob etwas anderes gemeint war.
Es ist wirklich schön, dass bestehender Code unverändert weiterläuft, obwohl so viele neue Funktionen hinzugekommen sind. Die kontinuierliche Anstrengung, Breaking Changes zu vermeiden, zahlt sich aus.
Wer mehr über Clojure erfahren möchte, sollte sich die Clojure/conj-Konferenz vom 23. bis 25. Oktober in Alexandria, Virginia, ansehen: https://2024.clojure-conj.org
Ich freue mich, dass add-libs und sync-deps hinzugekommen sind. Jetzt scheint es kaum noch oder gar keinen Grund mehr zu geben, eine Session unbedingt zu schließen.
Dieses Release wirkt vom Umfang her ziemlich anders als frühere Releases, und es ist interessant, weil so viel enthalten ist. Ich hoffe nur, dass es durch das höhere Tempo in ein paar Releases nicht zu einem verhedderten Klumpen wird.
Die Änderung bei funktionalen Interfaces ist riesig. Clojure ist am besten, wenn es durch sorgfältige Interoperabilität nah an Java bleibt, und diese Änderung schließt eine große Lücke.
Ich frage mich, was aus spec geworden ist. Wurde es aufgegeben? Gibt es etwas, worauf man sich freuen kann?
Sieht nach einem ziemlich soliden Release aus, und ich freue mich, dass Clojure weiterhin gut läuft.
Ich will nicht trollen. Ich möchte mich dafür entscheiden, und aus Engineering-Sicht wirkt es wie eine gute Entscheidung. Aber wenn Popularität und Zahl der Beitragenden stark zurückgehen, könnte das in naher Zukunft zum Problem werden.
[0] https://www.clara-rules.org/
Es ist einfacher denn je geworden, bestehende Entwickler zu Clojure-Entwicklern zu machen.
Das große Anfangsproblem[0] ist das Lesen von Code, und KI wie ChatGPT oder Claude ist sehr gut darin, bestehenden Clojure-Code zu erklären. Dadurch kann das Onboarding von Entwicklern deutlich schneller werden.
[0] Nach ein paar Wochen fühlt sich das Lesen von Clojure natürlich an, und man vergisst sogar, dass man es früher nicht lesen konnte.
Viele schöne Verbesserungen. Normalerweise ist es die wichtigste Lisp-Familien-Sprache, zu der ich greife.