Das Neuladen von Clojure-Code mit (require … :reload)
und :reload-all
ist sehr problematisch :
Wenn Sie zwei voneinander abhängige Namespaces ändern, müssen Sie daran denken, sie in der richtigen Reihenfolge neu zu laden, um Kompilierungsfehler zu vermeiden.
Wenn Sie Definitionen aus einer Quelldatei entfernen und dann neu laden, sind diese Definitionen weiterhin im Speicher verfügbar. Wenn anderer Code von diesen Definitionen abhängt, funktioniert er weiterhin, wird jedoch beim nächsten Neustart der JVM unterbrochen.
Wenn der neu geladene Namespace enthält defmulti
, müssen Sie auch alle zugehörigen defmethod
Ausdrücke neu laden .
Wenn der neu geladene Namespace enthält defprotocol
, müssen Sie auch alle Datensätze oder Typen, die dieses Protokoll implementieren, neu laden und vorhandene Instanzen dieser Datensätze / Typen durch neue Instanzen ersetzen.
Wenn der neu geladene Namespace Makros enthält, müssen Sie auch alle Namespaces neu laden, die diese Makros verwenden.
Wenn das laufende Programm Funktionen enthält, die Werte im neu geladenen Namespace schließen, werden diese geschlossenen Werte nicht aktualisiert. (Dies ist in Webanwendungen üblich, die den "Handler-Stack" als Zusammensetzung von Funktionen erstellen.)
Die Bibliothek clojure.tools.namespace verbessert die Situation erheblich. Es bietet eine einfache Aktualisierungsfunktion, die das intelligente Neuladen basierend auf einem Abhängigkeitsdiagramm der Namespaces ermöglicht.
myapp.web=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
myapp.web=> (refresh)
:reloading (myapp.web)
:ok
Leider schlägt das erneute Laden ein zweites Mal fehl, wenn sich der Namespace, in dem Sie auf die refresh
Funktion verwiesen haben , geändert hat. Dies liegt daran, dass tools.namespace die aktuelle Version des Namespace zerstört, bevor der neue Code geladen wird.
myapp.web=> (refresh)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: refresh in this context, compiling:(/private/var/folders/ks/d6qbfg2s6l1bcg6ws_6bq4600000gn/T/form-init819543191440017519.clj:1:1)
Sie könnten den vollständig qualifizierten Variablennamen als Problemumgehung für dieses Problem verwenden, aber ich persönlich bevorzuge es, dies nicht bei jeder Aktualisierung eingeben zu müssen. Ein weiteres Problem besteht darin, dass nach dem erneuten Laden des Hauptnamespace die Standard-REPL-Hilfsfunktionen (wie doc
und source
) dort nicht mehr referenziert werden.
Um diese Probleme zu lösen, bevorzuge ich es, eine tatsächliche Quelldatei für den Benutzernamensraum zu erstellen, damit diese zuverlässig neu geladen werden kann. Ich habe die Quelldatei eingefügt, ~/.lein/src/user.clj
aber Sie können sie überall ablegen. Die Datei sollte die Aktualisierungsfunktion in der Top-ns-Deklaration wie folgt erfordern:
(ns user
(:require [clojure.tools.namespace.repl :refer [refresh]]))
Sie können Setup ein Leiningen Benutzerprofil in ~/.lein/profiles.clj
so dass Speicherort der Datei setzen in dem Klassenpfad hinzugefügt wird. Das Profil sollte ungefähr so aussehen:
{:user {:dependencies [[org.clojure/tools.namespace "0.2.7"]]
:repl-options { :init-ns user }
:source-paths ["/Users/me/.lein/src"]}}
Beachten Sie, dass ich beim Starten der REPL den Benutzernamensraum als Einstiegspunkt festgelegt habe. Dadurch wird sichergestellt, dass die REPL-Hilfsfunktionen im Benutzernamensraum anstelle des Hauptnamensraums Ihrer Anwendung referenziert werden. Auf diese Weise gehen sie nur verloren, wenn Sie die gerade erstellte Quelldatei ändern.
Hoffe das hilft!
(use 'foo.bar :reload-all)
hat bei mir immer gut funktioniert. Auch(load-file)
sollte niemals erforderlich sein , wenn Sie Ihren Classpath richtig konfiguriert haben. Was ist der "erforderliche Effekt", den Sie nicht erhalten?