Як перезавантажити файл 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.
pkaleta

У вас повинна бути інша проблема в налаштуванні ... :reloadабо :reload-allповинні працювати обидва.
Джейсон

Відповіді:


196

Або (use 'your.namespace :reload)


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

77

Існує також така альтернатива, як використання інструментального простору імен. Це досить ефективно:

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 .
Дейв Ярвуд

1
Блогпост про робочий процес Clojure від автора tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
Девід Тонхофер

61

Перевантажувальний Clojure код , використовуючи (require … :reload)і :reload-allє досить проблематичним :

  • Якщо ви змінюєте два простори імен, які залежать один від одного, ви повинні пам'ятати, щоб перезавантажити їх у правильному порядку, щоб уникнути помилок компіляції.

  • Якщо ви вилучите визначення з вихідного файлу, а потім перезавантажте їх, ці визначення все ще доступні в пам'яті. Якщо інший код залежить від цих визначень, він буде продовжувати працювати, але порушиться при наступному перезапуску JVM.

  • Якщо перезавантажена область імен містить defmulti, ви також повинні перезавантажити всі пов'язані з ними defmethodвирази.

  • Якщо перезавантажена область імен містить defprotocol, ви також повинні перезавантажити будь-які записи чи типи, що реалізують цей протокол, та замінити всі наявні екземпляри цих записів / типів на нові екземпляри.

  • Якщо перезавантажена область імен містить макроси, ви також повинні перезавантажити будь-які простори імен, які використовують ці макроси.

  • Якщо запущена програма містить функції, які закривають більше значень у перезавантаженому просторі імен, ці закриті значення не оновлюються. (Це часто зустрічається у веб-додатках, які будують "стек обробника" як композицію функцій.)

Бібліотека простору clojure.tools.names значно покращує ситуацію. Він забезпечує функцію легкого оновлення, яка робить розумне перезавантаження на основі графіка залежності просторів імен.

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

На жаль, повторне завантаження не вдасться, якщо refreshзмінився простір імен, на який ви посилалися на функцію. Це пов’язано з тим, що space.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але його можна розмістити в будь-якому місці. Файл повинен вимагати функції оновлення у верхній 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.
Алан Томпсон


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)працює для вас, це означає, що у вас на CLASSPATH є foo / bar.clj або foo / bar_init.class. Bar_init.class буде версією bar.clj, складеної AOT. Якщо ви це робите (use 'foo.bar), я не точно впевнений, чи Clojure віддає перевагу класу над clj чи навпаки. Якщо б віддало перевагу файлам класу, а у вас є обидва файли, то зрозуміло, що редагування файлу clj та перезавантаження простору імен не впливають.

BTW: Вам не потрібно до load-fileтого, useякщо ваш CLASSPATH встановлений правильно.

BTW2: Якщо вам потрібно скористатися load-fileз причини, то ви можете просто зробити це ще раз, якщо ви редагували файл.


14
Не впевнений, чому це позначено як правильну відповідь. Це не дає чіткого відповіді на питання.
AnnanFay

5
Оскільки хтось підходить до цього питання, я не вважаю цю відповідь дуже зрозумілою.
ctford
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.