Как перезагрузить файл clojure в REPL

Каков предпочтительный способ перезагрузки функций, определенных в файле Clojure, без необходимости перезапуска REPL. Прямо сейчас, чтобы использовать обновленный файл, я должен:

  • редактировать src/foo/bar.clj
  • закрыть REPL
  • открыть ответ
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Кроме того, (use 'foo.bar :reload-all) не приводит к требуемому эффекту, который оценивает измененные тела функций и возвращает новые значения, а не ведет себя так, как источник вообще не изменился.

Документация:

Ответ 1

Или (use 'your.namespace :reload)

Ответ 2

Существует также альтернатива, использующая tools.namespace, она довольно эффективна:

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

user=> (refresh)

:reloading (namespace.app)

:ok

Ответ 3

Перезагрузка 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 будут ссылаться в пространстве имен пользователей вместо основного пространства имен вашего приложения. Таким образом, они не потеряются, если вы не измените исходный файл, который мы только что создали.

Надеюсь, это поможет!

Ответ 4

Лучший ответ:

(require 'my.namespace :reload-all)

Это не только перезагрузит указанное вами пространство имен, но также перезагрузит все пространства имен зависимостей.

Документация:

требовать

Ответ 5

Один вкладыш, основанный на ответе папачана:

(clojure.tools.namespace.repl/refresh)

Ответ 6

Я использую это в Lighttable (и в awesome instarepl), но он должен использоваться в других инструментах разработки. У меня была такая же проблема со старыми определениями функций и мультиметодов, зависающих после перезагрузки, так что теперь во время разработки вместо объявления пространств имен с помощью:

(ns my.namespace)

Я объявляю свои пространства имен следующим образом:

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

Довольно уродливый, но всякий раз, когда я переоцениваю все пространство имен (Cmd-Shift-Enter в Lighttable, чтобы получить новые результаты instarepl каждого выражения), он сбрасывает все старые определения и дает мне чистую среду. Перед тем, как я начал это делать, я сработал каждые несколько дней по старым определениям, и это спасло мое здравомыслие.:)

Ответ 7

Попробуйте загрузить файл снова?

Если вы используете IDE, обычно есть комбинация клавиш для отправки кода-кода в REPL, тем самым эффективно переопределяя связанные функции.

Ответ 8

Как только (use 'foo.bar) работает для вас, это означает, что у вас есть foo/bar.clj или foo/bar_init.class на вашем CLASSPATH. Bar_init.class будет AOT-скомпилированной версией bar.clj. Если вы выполняете (use 'foo.bar), я не совсем уверен, что Clojure предпочитает класс над clj или наоборот. Если он предпочтет файлы классов, и у вас есть оба файла, то ясно, что редактирование файла clj, а затем перезагрузка пространства имен не имеет никакого эффекта.

BTW: вам не нужно load-file перед use, если ваш CLASSPATH установлен правильно.

BTW2: Если вам нужно использовать load-file по какой-либо причине, вы можете просто сделать это снова, если вы отредактировали файл.