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


170

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

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

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

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


20
(use 'foo.bar :reload-all)всегда работал хорошо для меня. Кроме того, (load-file)никогда не должно быть необходимости, если у вас правильно настроен путь к классам. Какой «требуемый эффект» вы не получаете?
Дэйв Рэй

Да, что такое «требуемый эффект»? Разместите образец с bar.cljподробным описанием «требуемого эффекта».
Шридхар Ратнакумар

1
Под требуемым эффектом я имел в виду, что если у меня была функция (defn f [] 1)и я изменил ее определение на (defn f [] 2), мне казалось, что после того, как я (use 'foo.bar :reload-all)вызову и вызову fфункцию, она должна вернуть 2, а не 1. К сожалению, это не работает таким образом для меня и для всех Когда я меняю тело функции, мне нужно перезапустить REPL.
пкалета

У вас должна быть другая проблема в вашей настройке ... :reloadили :reload-allоба должны работать.
Джейсон

Ответы:


196

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


3
:reload-allтакже должен работать. ОП специально говорит, что это не так, но я думаю, что в среде разработчиков ОП было что-то еще не так, потому что для одного файла два ( :reloadи :reload-all) должны иметь одинаковый эффект. Вот полная команда для :reload-all: (use 'your.namespace :reload-all) Это также перезагружает все зависимости.
Джейсон

77

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

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

user=> (refresh)

:reloading (namespace.app)

:ok

3
этот ответ более правильный
Бахадир Камбел

12
Предостережение: запуск, (refresh)похоже, также заставляет REPL забыть о том, что вам нужно clojure.tools.namespace.repl. Последующие вызовы (refresh)создадут вам исключение RuntimeException «Не удалось разрешить символ: обновить в этом контексте». Вероятно, лучшее, что можно сделать, это либо либо (require 'your.namespace :reload-all), либо, если вы знаете, что хотите много обновить свой REPL для данного проекта, создать :devпрофиль и добавить [clojure.tools.namespace.repl :refer (refresh refresh-all)]вdev/user.clj .
Дэйв Ярвуд


61

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

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


Хорошие предложения. Один вопрос: почему запись ": source-paths" выше?
Алан Томпсон

2
@DirkGeurs, у :source-pathsменя получается #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, а у меня :resource-pathsвсе нормально.
fl00r

1
@ fl00r и все равно выдает эту ошибку? У вас есть действительный файл project.clj в папке, из которой вы запускаете REPL? Это может решить вашу проблему.
Дирк Гёрс

1
Да, это довольно стандартно, и все работает нормально :resource-paths, я нахожусь в моем пространстве имен пользователя внутри repl.
fl00r

1
Я просто прекрасно провел время, работая с REPL, который лгал мне из-за этой reloadпроблемы. Тогда оказалось, что все, что я думал, работало, больше не было. Может быть, кто-то должен исправить эту ситуацию?
Альпер

41

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

(require 'my.namespace :reload-all)

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

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

требовать


2
Это единственный ответ, который работал с lein replColjure 1.7.0 и nREPL 0.3.5. Если вы новичок в Clojure: Пространство имен ( 'my.namespace) определяется (ns ...)в src/... /core.clj, например.
Аарон Дигулла

1
Проблема с этим ответом состоит в том, что оригинальный вопрос использует (load-file ...), не требует. Как она может добавить: reload-all в пространство имен после файла загрузки?
jgomo3

Поскольку структура пространства имен похожа proj.stuff.coreна файловую структуру на диске, например src/proj/stuff/core.clj, REPL может найти правильный файл, и вам это не нужно load-file.
Алан Томпсон

6

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

(clojure.tools.namespace.repl/refresh)

5

Я использую это в Lighttable (и удивительном 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 для каждого выражения), оно уничтожает все старые определения и дает мне чистую среду. Меня опускали каждые несколько дней старые определения, прежде чем я начал это делать, и это спасло мое здравомыслие. :)


3

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

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


1

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

КСТАТИ: Вам не нужно load-fileперед тем , useесли ваш CLASSPATH установлен должным образом.

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


14
Не уверен, почему это помечено как правильный ответ. Это не дает четкого ответа на вопрос.
AnnanFay

5
Поскольку кто-то подходит к этому вопросу, я не нахожу этот ответ очень ясным.
ctford
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.