Cómo recargar un archivo clojure en REPL

171

¿Cuál es la forma preferida de recargar funciones definidas en un archivo Clojure sin tener que reiniciar el REPL? En este momento, para utilizar el archivo actualizado, tengo que:

  • editar src/foo/bar.clj
  • cerrar la REPL
  • abre el REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Además, (use 'foo.bar :reload-all)no genera el efecto requerido, que es evaluar los cuerpos modificados de funciones y devolver nuevos valores, en lugar de comportarse como si la fuente no hubiera cambiado en absoluto.

Documentación:

pkaleta
fuente
20
(use 'foo.bar :reload-all)siempre me ha funcionado bien. Además, (load-file)nunca debería ser necesario si tiene su classpath configurada correctamente. ¿Cuál es el "efecto requerido" que no obtiene?
Dave Ray
Sí, ¿cuál es el "efecto requerido"? Publique una muestra bar.cljdetallando el "efecto requerido".
Sridhar Ratnakumar
1
Por efecto requerido quise decir que si tenía una función (defn f [] 1)y cambiaba su definición a (defn f [] 2), me parecía que después de emitir (use 'foo.bar :reload-all)y llamar a la ffunción debería devolver 2, no 1. Desafortunadamente, no funciona de esa manera para mí y para todos vez que cambio el cuerpo de la función tengo que reiniciar el REPL.
pkaleta
Debe tener otro problema en su configuración ... :reloado :reload-allambos deberían funcionar.
Jason

Respuestas:

199

O (use 'your.namespace :reload)

Ming
fuente
3
:reload-alltambién debería funcionar. El OP dice específicamente que no lo hace, pero creo que había algo más mal en el entorno de desarrollo del OP porque para un solo archivo los dos ( :reloady :reload-all) deberían tener el mismo efecto. Aquí está el comando completo para :reload-all: (use 'your.namespace :reload-all) Esto también recarga todas las dependencias.
Jason
77

También hay una alternativa como usar tools.namespace , es bastante eficiente:

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

user=> (refresh)

:reloading (namespace.app)

:ok
papachan
fuente
3
esta respuesta es más adecuada
Bahadir Cambel
12
Advertencia: la ejecución (refresh)también parece hacer que el REPL olvide lo que ha requerido clojure.tools.namespace.repl. Las llamadas posteriores a (refresh)le darán una RuntimeException, "No se puede resolver el símbolo: actualizar en este contexto". Probablemente lo mejor que puede hacer es o bien (require 'your.namespace :reload-all), o, si se sabe que va a querer refrescar la REPL mucho para un proyecto dado, hacer un :devperfil y añadir [clojure.tools.namespace.repl :refer (refresh refresh-all)]adev/user.clj .
Dave Yarwood
1
Publicación de blog sobre el flujo de trabajo de Clojure del autor de tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer
61

Recargar el código de Clojure usando (require … :reload)y :reload-alles muy problemático :

  • Si modifica dos espacios de nombres que dependen el uno del otro, debe recordar volver a cargarlos en el orden correcto para evitar errores de compilación.

  • Si elimina definiciones de un archivo de origen y luego las vuelve a cargar, esas definiciones aún estarán disponibles en la memoria. Si otro código depende de esas definiciones, seguirá funcionando pero se interrumpirá la próxima vez que reinicie la JVM.

  • Si el espacio de nombres recargado contiene defmulti, también debe recargar todas las defmethodexpresiones asociadas .

  • Si el espacio de nombres recargado contiene defprotocol, también debe recargar cualquier registro o tipo que implemente ese protocolo y reemplazar cualquier instancia existente de esos registros / tipos con nuevas instancias.

  • Si el espacio de nombres recargado contiene macros, también debe volver a cargar cualquier espacio de nombres que utilice esas macros.

  • Si el programa en ejecución contiene funciones que cierran sobre valores en el espacio de nombres recargado, esos valores cerrados no se actualizan. (Esto es común en aplicaciones web que construyen la "pila de manejadores" como una composición de funciones).

La biblioteca clojure.tools.namespace mejora la situación significativamente. Proporciona una función de actualización sencilla que realiza una recarga inteligente basada en un gráfico de dependencia de los espacios de nombres.

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

Desafortunadamente, la recarga por segunda vez fallará si el espacio de nombres en el que hizo referencia a la refreshfunción cambió. Esto se debe al hecho de que tools.namespace destruye la versión actual del espacio de nombres antes de cargar el nuevo código.

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)

Puede usar el nombre var completamente calificado como solución para este problema, pero personalmente prefiero no tener que escribirlo en cada actualización. Otro problema con lo anterior es que después de recargar el espacio de nombres principal, ya no se hace referencia a las funciones auxiliares estándar de REPL (como docy source).

Para resolver estos problemas, prefiero crear un archivo fuente real para el espacio de nombres del usuario para que pueda recargarse de manera confiable. Puse el archivo fuente, ~/.lein/src/user.cljpero puedes colocarlo en cualquier lugar. El archivo debería requerir la función de actualización en la declaración ns superior como esta:

(ns user
  (:require [clojure.tools.namespace.repl :refer [refresh]]))

Puede configurar un perfil de usuario Leiningen de ~/.lein/profiles.cljmodo que la ubicación de poner el archivo en se añade a la ruta de clase. El perfil debería verse así:

{:user {:dependencies [[org.clojure/tools.namespace "0.2.7"]]
        :repl-options { :init-ns user }
        :source-paths ["/Users/me/.lein/src"]}}

Tenga en cuenta que configuro el espacio de nombres de usuario como punto de entrada al iniciar REPL. Esto asegura que las funciones auxiliares de REPL sean referenciadas en el espacio de nombres de usuario en lugar del espacio de nombres principal de su aplicación. De esa manera, no se perderán a menos que modifique el archivo fuente que acabamos de crear.

¡Espero que esto ayude!

Dirk Geurs
fuente
Buenas sugerencias. Una pregunta: ¿por qué la entrada ": source -path" de arriba?
Alan Thompson
2
@DirkGeurs, con :source-pathsI get #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, mientras que con :resource-pathstodo está bien.
fl00r
1
@ fl00r y todavía arroja ese error? ¿Tiene un project.clj válido en la carpeta desde la que está iniciando el REPL? Eso podría solucionar su problema.
Dirk Geurs
1
Sí, es bastante estándar y todo funciona bien con :resource-paths, estoy en mi espacio de nombres de usuario dentro de repl.
fl00r
1
Lo pasé muy bien trabajando con un REPL que me estaba mintiendo debido a este reloadproblema. Luego resultó que todo lo que pensaba que estaba funcionando ya no funcionaba. ¿Quizás alguien debería arreglar esta situación?
Alper
41

La mejor respuesta es:

(require 'my.namespace :reload-all)

Esto no solo recargará su espacio de nombres especificado, sino que también recargará todos los espacios de nombres de dependencia.

Documentación:

exigir

Alan Thompson
fuente
2
Esta es la única respuesta que funcionó con lein replColjure 1.7.0 y nREPL 0.3.5. Si es nuevo en clojure: el espacio de nombres ( 'my.namespace) se define con (ns ...)in src/... /core.clj, por ejemplo.
Aaron Digulla
1
El problema con esta respuesta es que la pregunta original está usando (cargar-archivo ...), no es necesario. ¿Cómo puede ella agregar: reload-all al espacio de nombres después del archivo de carga?
jgomo3
Debido a que la estructura del espacio de nombres proj.stuff.corerefleja la estructura del archivo en el disco src/proj/stuff/core.clj, el REPL puede localizar el archivo correcto y usted no lo necesita load-file.
Alan Thompson
6

Una línea basada en la respuesta de papachan:

(clojure.tools.namespace.repl/refresh)
Jiezhen Yi
fuente
5

Lo uso en Lighttable (y el increíble instarepl) pero debería ser útil en otras herramientas de desarrollo. Estaba teniendo el mismo problema con las definiciones antiguas de funciones y métodos múltiples que se encuentran después de las recargas, así que ahora durante el desarrollo en lugar de declarar espacios de nombres con:

(ns my.namespace)

Declaro mis espacios de nombres así:

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

Bastante feo, pero cada vez que vuelvo a evaluar todo el espacio de nombres (Cmd-Shift-Enter en Lighttable para obtener los nuevos resultados de instarepl de cada expresión), destruye todas las definiciones antiguas y me brinda un entorno limpio. Antes de empezar a hacer esto, me tropezaba cada pocos días con definiciones antiguas y me ha salvado la cordura. :)

optevo
fuente
3

¿Intentar cargar archivo de nuevo?

Si está utilizando un IDE, generalmente hay un atajo de teclado para enviar un bloque de código al REPL, redefiniendo así de manera efectiva las funciones asociadas.

Paul Lam
fuente
1

Tan pronto como (use 'foo.bar)funcione para usted, significa que tiene foo / bar.clj o foo / bar_init.class en su CLASSPATH. Bar_init.class sería una versión compilada por AOT de bar.clj. Si lo haces(use 'foo.bar) , no estoy exactamente seguro de si Clojure prefiere la clase sobre clj o al revés. Si prefiere archivos de clase y tiene ambos archivos, entonces está claro que editar el archivo clj y luego volver a cargar el espacio de nombres no tiene ningún efecto.

Por cierto: no es necesario load-fileantes deluse si su CLASSPATH está configurado correctamente.

BTW2: Si necesita usarlo load-filepor alguna razón, simplemente puede hacerlo nuevamente si editó el archivo.

Cuerno de Tassilo
fuente
14
No estoy seguro de por qué está marcado como la respuesta correcta. No responde la pregunta con claridad.
AnnanFay
5
Como alguien que llega a esta pregunta, no encuentro esta respuesta muy clara.
ctford