Jak ponownie załadować plik clojure w REPL

170

Jaki jest preferowany sposób ponownego ładowania funkcji zdefiniowanych w pliku Clojure bez konieczności ponownego uruchamiania REPL. W tej chwili, aby skorzystać ze zaktualizowanego pliku, muszę:

  • edytować src/foo/bar.clj
  • zamknij REPL
  • otwórz REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Ponadto (use 'foo.bar :reload-all)nie daje wymaganego efektu, który polega na ocenie zmodyfikowanych treści funkcji i zwróceniu nowych wartości, zamiast zachowywać się, jakby źródło w ogóle się nie zmieniło.

Dokumentacja:

pkaleta
źródło
20
(use 'foo.bar :reload-all)zawsze działało dobrze dla mnie. Ponadto (load-file)nie powinno być nigdy konieczne, jeśli masz prawidłowo skonfigurowaną ścieżkę klas. Jaki jest „wymagany efekt”, którego nie osiągasz?
Dave Ray
Tak, jaki jest „wymagany efekt”? Opublikuj próbkę bar.cljopisującą „wymagany efekt”.
Sridhar Ratnakumar
1
Przez wymagany efekt miałem na myśli, że gdybym miał funkcję (defn f [] 1)i zmieniłem jej definicję na (defn f [] 2), wydawało mi się, że po wydaniu (use 'foo.bar :reload-all)i wywołaniu ffunkcji powinna zwrócić 2, a nie 1. Niestety nie działa tak dla mnie i dla wszystkich gdy zmieniam korpus funkcji muszę ponownie uruchomić REPL.
pkaleta
Musisz mieć inny problem w konfiguracji ... :reloadlub :reload-alloba powinny działać.
Jason

Odpowiedzi:

196

Lub (use 'your.namespace :reload)

Ming
źródło
3
:reload-allpowinien również działać. OP wyraźnie mówi, że tak nie jest, ale myślę, że w środowisku programistycznym OP było coś nie tak, ponieważ w przypadku jednego pliku te dwa ( :reloadi :reload-all) powinny mieć ten sam efekt. Oto pełne polecenie :reload-all: (use 'your.namespace :reload-all) To również przeładowuje wszystkie zależności.
Jason
77

Istnieje również alternatywa, taka jak użycie tools.namespace , jest całkiem wydajna:

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

user=> (refresh)

:reloading (namespace.app)

:ok
papaczan
źródło
3
ta odpowiedź jest bardziej właściwa
Bahadir Cambel,
12
Uwaga: uruchomienie (refresh)wydaje się również powodować, że REPL zapomina, że ​​jest to wymagane clojure.tools.namespace.repl. Kolejne wywołania wywołania wywołania (refresh)RuntimeException „Nie można rozpoznać symbolu: odśwież w tym kontekście”. Prawdopodobnie najlepszą rzeczą do zrobienia jest jedno (require 'your.namespace :reload-all)lub drugie , lub, jeśli wiesz, że będziesz chciał często odświeżać swoją REPL dla danego projektu, utwórz :devprofil i dodaj [clojure.tools.namespace.repl :refer (refresh refresh-all)]do niegodev/user.clj .
Dave Yarwood
1
Post na blogu dotyczący przepływu pracy Clojure przez autora tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer
61

Ponowne ładowanie kodu Clojure przy użyciu (require … :reload)i :reload-alljest bardzo problematyczne :

  • Jeśli zmodyfikujesz dwie przestrzenie nazw, które są od siebie zależne, musisz pamiętać o ich ponownym załadowaniu we właściwej kolejności, aby uniknąć błędów kompilacji.

  • Jeśli usuniesz definicje z pliku źródłowego, a następnie załadujesz go ponownie, te definicje będą nadal dostępne w pamięci. Jeśli inny kod zależy od tych definicji, będzie nadal działał, ale zepsuje się przy następnym ponownym uruchomieniu maszyny JVM.

  • Jeśli ponownie załadowana przestrzeń nazw zawiera defmulti, należy również ponownie załadować wszystkie skojarzone z nią defmethodwyrażenia.

  • Jeśli przeładowana przestrzeń nazw zawiera defprotocol, należy również ponownie załadować wszelkie rekordy lub typy implementujące ten protokół i zastąpić wszystkie istniejące instancje tych rekordów / typów nowymi instancjami.

  • Jeśli przeładowana przestrzeń nazw zawiera makra, należy również ponownie załadować wszystkie przestrzenie nazw, które używają tych makr.

  • Jeśli uruchomiony program zawiera funkcje, które zamykają wartości w przeładowywanej przestrzeni nazw, te zamknięte wartości nie są aktualizowane. (Jest to powszechne w aplikacjach internetowych, które konstruują „stos obsługi” jako kompozycję funkcji).

Biblioteka clojure.tools.namespace znacznie poprawia sytuację. Zapewnia łatwą funkcję odświeżania, która wykonuje inteligentne ponowne ładowanie w oparciu o wykres zależności przestrzeni nazw.

myapp.web=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
myapp.web=> (refresh)
:reloading (myapp.web)
:ok

Niestety ponowne załadowanie nie powiedzie się, jeśli przestrzeń nazw, w której odwołujesz się do refreshfunkcji, ulegnie zmianie. Wynika to z faktu, że tools.namespace niszczy aktualną wersję przestrzeni nazw przed załadowaniem nowego kodu.

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)

Możesz użyć w pełni kwalifikowanej nazwy var jako obejścia tego problemu, ale osobiście wolę nie wpisywać tego przy każdym odświeżaniu. Innym problemem związanym z powyższym jest to, że po przeładowaniu głównej przestrzeni nazw nie ma tam odwołań do standardowych funkcji pomocniczych REPL (takich jak doci source).

Aby rozwiązać te problemy, wolę utworzyć rzeczywisty plik źródłowy dla przestrzeni nazw użytkownika, aby można go było niezawodnie ponownie załadować. Umieściłem plik źródłowy, ~/.lein/src/user.cljale możesz go umieścić w dowolnym miejscu. Plik powinien wymagać funkcji odświeżania w górnej deklaracji ns w następujący sposób:

(ns user
  (:require [clojure.tools.namespace.repl :refer [refresh]]))

Możesz skonfigurować profil użytkownika leiningen w ~/.lein/profiles.cljtak, aby lokalizacja, w której umieściłeś plik, była dodawana do ścieżki klasy. Profil powinien wyglądać mniej więcej tak:

{:user {:dependencies [[org.clojure/tools.namespace "0.2.7"]]
        :repl-options { :init-ns user }
        :source-paths ["/Users/me/.lein/src"]}}

Zauważ, że ustawiłem przestrzeń nazw użytkownika jako punkt wejścia podczas uruchamiania REPL. Gwarantuje to, że do funkcji pomocniczych REPL będą odwoływać się przestrzeń nazw użytkownika, a nie główna przestrzeń nazw aplikacji. W ten sposób nie zgubią się, chyba że zmienisz plik źródłowy, który właśnie utworzyliśmy.

Mam nadzieję że to pomoże!

Dirk Geurs
źródło
Dobre sugestie. Jedno pytanie: dlaczego powyższy wpis „: source-path”?
Alan Thompson,
2
@DirkGeurs, z :source-pathsdostaję #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, podczas gdy ze :resource-pathswszystkim jest w porządku.
fl00r
1
@ fl00r i nadal zgłasza ten błąd? Czy masz prawidłowy plik project.clj w folderze, z którego uruchamiasz REPL? To może rozwiązać twój problem.
Dirk Geurs
1
Tak, jest dość standardowy i wszystko działa dobrze :resource-paths, jestem w mojej przestrzeni nazw użytkownika wewnątrz repl.
fl00r
1
Po prostu świetnie się bawiłem, pracując z REPL, który okłamał mnie z powodu tego reloadproblemu. Potem okazało się, że wszystko, o czym myślałem, że działa, już nie działa. Może ktoś powinien naprawić tę sytuację?
Alper
41

Najlepsza odpowiedź to:

(require 'my.namespace :reload-all)

Spowoduje to nie tylko ponowne załadowanie określonej przestrzeni nazw, ale także przeładuje wszystkie przestrzenie nazw zależności.

Dokumentacja:

wymagać

Alan Thompson
źródło
2
To jedyna odpowiedź, która działała z lein replColjure 1.7.0 i nREPL 0.3.5. Jeśli nie masz doświadczenia z clojure: przestrzeń nazw ( 'my.namespace) jest zdefiniowana na przykład za pomocą (ns ...)in src/.... /core.clj
Aaron Digulla
1
Problem z tą odpowiedzią polega na tym, że pierwotne pytanie używa (załaduj plik ...), nie wymaga. Jak może dodać: reload-all do przestrzeni nazw po załadowanym pliku?
jgomo3
Ponieważ struktura przestrzeni nazw, taka jak proj.stuff.coreodzwierciedla strukturę plików na dysku src/proj/stuff/core.clj, taka jak REPL może zlokalizować właściwy plik, którego nie potrzebujesz load-file.
Alan Thompson
6

Jedna linijka oparta na odpowiedzi Papachana:

(clojure.tools.namespace.repl/refresh)
Jiezhen Yi
źródło
5

Używam tego w Lighttable (i niesamowitym instarepl), ale powinno to być przydatne w innych narzędziach programistycznych. Miałem ten sam problem ze starymi definicjami funkcji i multimetod, które kręciły się po przeładowaniach, więc teraz podczas programowania zamiast deklarować przestrzenie nazw za pomocą:

(ns my.namespace)

Deklaruję moje przestrzenie nazw w następujący sposób:

(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))

Dość brzydkie, ale ilekroć ponownie oceniam całą przestrzeń nazw (Cmd-Shift-Enter w Lighttable, aby uzyskać nowe wyniki instarepl dla każdego wyrażenia), usuwa wszystkie stare definicje i zapewnia mi czyste środowisko. Co kilka dni potykały mnie stare definicje, zanim zacząłem to robić, co uratowało mi zdrowie psychiczne. :)

optevo
źródło
3

Spróbuj ponownie załadować plik?

Jeśli używasz IDE, zwykle istnieje skrót klawiaturowy do wysyłania bloku kodu do REPL, co skutecznie definiuje powiązane funkcje.

Paul Lam
źródło
1

Gdy tylko (use 'foo.bar)zadziała, oznacza to, że masz foo / bar.clj lub foo / bar_init.class na swojej CLASSPATH. Bar_init.class byłby skompilowaną przez AOT wersją bar.clj. Jeśli tak (use 'foo.bar), nie jestem do końca pewien, czy Clojure woli klasę niż clj, czy na odwrót. Jeśli wolałby pliki klas i masz oba pliki, jasne jest, że edycja pliku clj, a następnie ponowne załadowanie przestrzeni nazw nie ma żadnego efektu.

BTW: Nie musisz tego robić load-fileprzed, usejeśli CLASSPATH jest ustawiony poprawnie.

BTW2: Jeśli chcesz użyć load-filez jakiegoś powodu, możesz po prostu zrobić to ponownie, jeśli edytowałeś plik.

Tassilo Horn
źródło
14
Nie wiem, dlaczego jest to poprawna odpowiedź. Nie daje jasnej odpowiedzi na pytanie.
AnnanFay,
5
Jako ktoś, kto przychodzi na to pytanie, nie uważam tej odpowiedzi za bardzo jasną.
ctford,