Ponowne ładowanie kodu Clojure przy użyciu (require … :reload)
i :reload-all
jest 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ą defmethod
wyraż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 refresh
funkcji, 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 doc
i 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.clj
ale 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.clj
tak, 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!
(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?