So laden Sie eine Clojure-Datei in REPL neu

170

Was ist die bevorzugte Methode zum erneuten Laden von Funktionen, die in einer Clojure-Datei definiert sind, ohne dass die REPL neu gestartet werden muss? Um die aktualisierte Datei verwenden zu können, muss ich Folgendes tun:

  • bearbeiten src/foo/bar.clj
  • Schließen Sie die REPL
  • Öffnen Sie die REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Darüber hinaus führt (use 'foo.bar :reload-all)dies nicht zu einem erforderlichen Effekt, bei dem die geänderten Funktionskörper ausgewertet und neue Werte zurückgegeben werden, anstatt sich so zu verhalten, als hätte sich die Quelle überhaupt nicht geändert.

Dokumentation:

pkaleta
quelle
20
(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?
Dave Ray
Ja, was ist der "erforderliche Effekt"? Veröffentlichen Sie ein Beispiel mit bar.cljDetails zum "erforderlichen Effekt".
Sridhar Ratnakumar
1
Mit dem erforderlichen Effekt meinte ich, wenn ich eine Funktion hatte (defn f [] 1)und ihre Definition in änderte (defn f [] 2), schien es mir, dass nach dem Ausgeben (use 'foo.bar :reload-all)und Aufrufen der fFunktion 2 und nicht 1 zurückgegeben werden sollte. Leider funktioniert dies für mich und alle nicht so Wenn ich den Funktionskörper ändere, muss ich die REPL neu starten.
pkaleta
Sie müssen ein anderes Problem in Ihrem Setup haben ... :reloadoder :reload-allsollten beide funktionieren.
Jason

Antworten:

196

Oder (use 'your.namespace :reload)

Ming
quelle
3
:reload-allsollte auch funktionieren. Das OP sagt ausdrücklich, dass dies nicht der Fall ist, aber ich denke, dass in der Entwicklungsumgebung des OP etwas anderes nicht stimmte, da für eine einzelne Datei die beiden ( :reloadund :reload-all) den gleichen Effekt haben sollten. Hier ist der vollständige Befehl für :reload-all: (use 'your.namespace :reload-all) Dadurch werden auch alle Abhängigkeiten neu geladen.
Jason
77

Es gibt auch eine Alternative wie die Verwendung von tools.namespace , die ziemlich effizient ist:

user=> (use '[clojure.tools.namespace.repl :only (refresh)])

user=> (refresh)

:reloading (namespace.app)

:ok
Papachan
quelle
3
Diese Antwort ist richtiger
Bahadir Cambel
12
Vorsichtsmaßnahme: Beim Laufen (refresh)scheint die REPL auch zu vergessen, dass Sie sie benötigt haben clojure.tools.namespace.repl. Bei nachfolgenden Aufrufen von erhalten (refresh)Sie eine RuntimeException: "Symbol kann nicht aufgelöst werden: Aktualisierung in diesem Kontext." Wahrscheinlich ist es das Beste, entweder (require 'your.namespace :reload-all)oder, wenn Sie wissen, dass Sie Ihre REPL für ein bestimmtes Projekt häufig aktualisieren möchten , ein :devProfil [clojure.tools.namespace.repl :refer (refresh refresh-all)]zu erstellen und zu ergänzendev/user.clj .
Dave Yarwood
1
Blogpost über den Clojure-Workflow des Autors von tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer
61

Das Neuladen von Clojure-Code mit (require … :reload)und :reload-allist 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 defmethodAusdrü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 refreshFunktion 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 docund 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.cljaber 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.cljso 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!

Dirk Geurs
quelle
Gute Vorschläge. Eine Frage: Warum der Eintrag ": source-path" oben?
Alan Thompson
2
@DirkGeurs, mit dem :source-pathsich bekomme #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, während mit :resource-pathsallem alles in Ordnung ist.
Fl00r
1
@ fl00r und es wirft immer noch diesen Fehler? Haben Sie eine gültige project.clj in dem Ordner, aus dem Sie die REPL starten? Das könnte Ihr Problem beheben.
Dirk Geurs
1
Ja, es ist ziemlich :resource-pathsnormal und alles funktioniert einwandfrei. Ich bin in meinem Benutzernamensraum in repl.
fl00r
1
Ich hatte einfach eine großartige Zeit mit einer REPL zu arbeiten, die mich wegen dieses reloadProblems angelogen hat. Dann stellte sich heraus, dass alles, was ich für funktionierend hielt, nicht mehr funktionierte. Vielleicht sollte jemand diese Situation beheben?
Alper
41

Die beste Antwort ist:

(require 'my.namespace :reload-all)

Dadurch wird nicht nur der angegebene Namespace neu geladen, sondern auch alle Abhängigkeitsnamespaces neu geladen.

Dokumentation:

benötigen

Alan Thompson
quelle
2
Dies ist die einzige Antwort, die mit lein replColjure 1.7.0 und nREPL 0.3.5 funktioniert hat. Wenn Sie neu in Clojure sind: Der Namespace ( 'my.namespace) wird beispielsweise mit (ns ...)in src/... definiert /core.clj.
Aaron Digulla
1
Das Problem bei dieser Antwort ist, dass die ursprüngliche Frage verwendet (Datei laden ...), nicht erforderlich. Wie kann sie das: reload-all nach der Ladedatei zum Namespace hinzufügen?
jgomo3
Da die Namespace-Struktur wie proj.stuff.coredie Dateistruktur auf der Festplatte wie spiegelt src/proj/stuff/core.clj, kann die REPL die richtige Datei finden und Sie benötigen sie nicht load-file.
Alan Thompson
6

Ein Liner basierend auf Papachans Antwort:

(clojure.tools.namespace.repl/refresh)
Jiezhen Yi
quelle
5

Ich verwende dies in Lighttable (und dem fantastischen Instarepl), aber es sollte in anderen Entwicklungstools von Nutzen sein. Ich hatte das gleiche Problem mit alten Definitionen von Funktionen und Multimethoden, die nach dem Neuladen herumhängen, also jetzt während der Entwicklung, anstatt Namespaces zu deklarieren mit:

(ns my.namespace)

Ich deklariere meine Namespaces wie folgt:

(clojure.core/let [s 'my.namespace]
                  (clojure.core/remove-ns s)
                  (clojure.core/in-ns s)
                  (clojure.core/require '[clojure.core])
                  (clojure.core/refer 'clojure.core))

Ziemlich hässlich, aber wenn ich den gesamten Namespace neu bewerte (Cmd-Shift-Enter in Lighttable, um die neuen instarepl-Ergebnisse jedes Ausdrucks zu erhalten), werden alle alten Definitionen weggeblasen und ich bekomme eine saubere Umgebung. Ich wurde alle paar Tage von alten Definitionen gestolpert, bevor ich damit anfing, und es hat meine geistige Gesundheit gerettet. :) :)

optevo
quelle
3

Versuchen Sie erneut, die Datei zu laden?

Wenn Sie eine IDE verwenden, gibt es normalerweise eine Tastenkombination, um einen Codeblock an die REPL zu senden und so die zugehörigen Funktionen effektiv neu zu definieren.

Paul Lam
quelle
1

Sobald dies (use 'foo.bar)für Sie funktioniert, bedeutet dies, dass Sie foo / bar.clj oder foo / bar_init.class auf Ihrem CLASSPATH haben. Die bar_init.class wäre eine AOT-kompilierte Version von bar.clj. Wenn Sie das tun (use 'foo.bar), bin ich mir nicht ganz sicher, ob Clojure den Unterricht gegenüber clj bevorzugt oder umgekehrt. Wenn es Klassendateien bevorzugen würde und Sie beide Dateien haben, ist es klar, dass das Bearbeiten der clj-Datei und das anschließende Neuladen des Namespace keine Auswirkungen hat.

Übrigens: Sie müssen nicht load-filevor dem, usewenn Ihr CLASSPATH richtig eingestellt ist.

Übrigens: Wenn Sie es load-fileaus einem bestimmten Grund verwenden müssen, können Sie es einfach erneut ausführen , wenn Sie die Datei bearbeitet haben.

Tassilo Horn
quelle
14
Ich bin mir nicht sicher, warum dies als die richtige Antwort markiert ist. Es beantwortet die Frage nicht klar.
AnnanFay
5
Als jemand, der zu dieser Frage kommt, finde ich diese Antwort nicht sehr klar.
Ctford