Повторная загрузка кода Clojure с использованием (require … :reload)
и :reload-all
является очень проблематичной :
Если вы изменяете два пространства имен, которые зависят друг от друга, вы должны
не забудьте перезагрузить их в правильном порядке, чтобы избежать компиляции
ошибки.
Если вы удалите определения из исходного файла, а затем перезагрузите его,
эти определения все еще доступны в памяти. Если другой код
зависит от этих определений, он будет продолжать работать, но будет
в следующий раз перезапустите JVM.
Если перезагруженное пространство имен содержит defmulti
, необходимо также перезагрузить
все связанные defmethod
выражения.
Если перезагруженное пространство имен содержит defprotocol
, необходимо также
перезагрузите все записи или типы, реализующие этот протокол, и замените
любые существующие экземпляры этих записей / типов с новыми экземплярами.
Если перезагруженное пространство имен содержит макросы, необходимо также перезагрузить любой
пространства имен, использующие эти макросы.
Если запущенная программа содержит функции, которые закрывают значения в
перезагруженное пространство имен, эти закрытые значения не обновляются.
(Это часто встречается в веб-приложениях, которые создают «обработчик»
стек »как композиция функций.)
Библиотека clojure.tools.namespace значительно улучшает ситуацию. Он предоставляет функцию простого обновления, которая выполняет интеллектуальную перезагрузку на основе графа зависимостей пространств имен.
myapp.web=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
myapp.web=> (refresh)
:reloading (myapp.web)
:ok
К сожалению, перезагрузка во второй раз не удастся, если изменилось пространство имен, в котором вы ссылались на функцию refresh
. Это связано с тем, что tools.namespace уничтожает текущую версию пространства имен перед загрузкой нового кода.
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)
Вы можете использовать полное имя var в качестве обходного пути для этой проблемы, но лично я предпочитаю не вводить это при каждом обновлении. Другая проблема, связанная с вышеизложенным, заключается в том, что после перезагрузки основного пространства имен на стандартные вспомогательные функции REPL (например, doc
и source
) больше нет ссылок.
Для решения этих проблем я предпочитаю создать фактический исходный файл для пространства имен пользователя, чтобы его можно было надежно перезагрузить. Я поместил исходный файл в ~/.lein/src/user.clj
, но вы можете разместить в любом месте. Файл должен требовать функцию обновления в объявлении top ns, например:
(ns user
(:require [clojure.tools.namespace.repl :refer [refresh]]))
Вы можете настроить профиль пользователя leiningen в ~/.lein/profiles.clj
, чтобы местоположение, в которое вы помещаете файл, добавлялось в путь к классам. Профиль должен выглядеть примерно так:
{:user {:dependencies [[org.clojure/tools.namespace “0.2.7”]]
:repl-options { :init-ns user }
:source-paths [“/Users/me/.lein/src”]}}
Обратите внимание, что я установил пространство имен пользователя в качестве точки входа при запуске REPL. Это гарантирует, что на вспомогательные функции REPL будут ссылаться в пространстве имен пользователя вместо основного пространства имен вашего приложения. Таким образом, они не потеряются, если вы не измените исходный файл, который мы только что создали.
Надеюсь, это поможет!