tutorial español ejemplos clojure reload read-eval-print-loop leiningen

español - Cómo volver a cargar un archivo clojure en REPL



clojure tutorial español (8)

Intente 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 efectivamente las funciones asociadas.

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

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

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


La mejor respuesta es:

(require ''my.namespace :reload-all)

Esto no solo volverá a cargar el espacio de nombres especificado, sino que también volverá a cargar todos los espacios de nombres de las dependencias.


O bien (use ''your.namespace :reload)


Recargar el código de Clojure usando (require … :reload) y :reload-all es 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 fuente y luego las vuelve a cargar, esas definiciones todavía están disponibles en la memoria. Si otro código depende de esas definiciones, continuará funcionando pero se interrumpirá la próxima vez que reinicie la JVM.

  • Si el espacio de nombre recargado contiene defmulti , también debe volver a cargar todas las expresiones de defmethod asociadas.

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

  • Si el espacio de nombre recargado contiene macros, también debe volver a cargar los espacios de nombres que usen esas macros.

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

La biblioteca clojure.tools.namespace mejora la situación significativamente. Proporciona una función de actualización fácil 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

Lamentablemente, volver a cargar una segunda vez fallará si cambia el espacio de nombre en el que hizo referencia a la función de refresh . 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)

Podría usar el nombre de var completamente calificado como una 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 volver a cargar el espacio de nombres principal, las funciones de ayuda REPL estándar (como doc y source ) ya no se referencian allí.

Para resolver estos problemas, prefiero crear un archivo fuente real para el espacio de nombres de usuario para que pueda volver a cargarse de manera confiable. Puse el archivo fuente en ~/.lein/src/user.clj pero puede colocarlo en cualquier lugar. El archivo debe 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 de Leiningen en ~/.lein/profiles.clj para que la ubicación donde ~/.lein/profiles.clj el archivo se agregue a la ruta de la clase. El perfil debería verse más o menos 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 el punto de entrada cuando ejecuto REPL. Esto garantiza que las funciones de ayuda REPL se mencionen en el espacio de nombres de usuario en lugar del espacio de nombres principal de la aplicación. De esa forma no se perderán a menos que modifiques el archivo fuente que acabamos de crear.

¡Espero que esto ayude!


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


Tan pronto como (use ''foo.bar) funcione, significa que tiene foo / bar.clj o foo / bar_init.class en su CLASSPATH. Bar_init.class sería una versión compilada de AOT de bar.clj. Si lo haces (use ''foo.bar) , no estoy exactamente seguro si Clojure prefiere la clase sobre clj o al revés. Si preferiría los archivos de clase y usted 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 necesita load-file antes de use si su CLASSPATH está configurada correctamente.

BTW2: si necesita utilizar load-file por algún motivo, simplemente puede hacerlo nuevamente si editó el archivo.


Un trazador de líneas basado en la respuesta de Papachan:

(clojure.tools.namespace.repl/refresh)


Utilizo esto en Lighttable (y en el impresionante instarepl) pero debería ser útil en otras herramientas de desarrollo. Estaba teniendo el mismo problema con las antiguas definiciones de funciones y multimétodos dando vueltas después de volver a cargar, por lo 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 reevalúo el espacio de nombre completo (Cmd-Shift-Enter en Lighttable para obtener los nuevos resultados de instarepl de cada expresión), destruye todas las definiciones antiguas y me da un entorno limpio. Me tropecé cada pocos días por definiciones antiguas antes de comenzar a hacer esto y me ha salvado la cordura. :)