Comment recharger un fichier clojure dans REPL


170

Quelle est la manière préférée de recharger les fonctions définies dans un fichier Clojure sans avoir à redémarrer le REPL. Pour le moment, pour utiliser le fichier mis à jour, je dois:

  • Éditer src/foo/bar.clj
  • fermer le REPL
  • ouvrir le REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

De plus, (use 'foo.bar :reload-all)n'entraîne pas l'effet requis, qui évalue les corps modifiés des fonctions et renvoie de nouvelles valeurs, au lieu de se comporter comme la source n'a pas du tout changé.

Documentation:


20
(use 'foo.bar :reload-all)a toujours bien fonctionné pour moi. De plus, cela (load-file)ne devrait jamais être nécessaire si votre chemin de classe est correctement configuré. Quel est l '«effet requis» que vous n'obtenez pas?
Dave Ray

Oui, quel est «l'effet requis»? Postez un échantillon bar.cljdétaillant "l'effet requis".
Sridhar Ratnakumar

1
Par effet requis, je voulais dire que si j'avais une fonction (defn f [] 1)et que je changeais sa définition en (defn f [] 2), il me semblait qu'après avoir émis (use 'foo.bar :reload-all)et appelé la ffonction, elle devrait renvoyer 2, pas 1. Malheureusement, cela ne fonctionne pas de cette façon pour moi et chaque fois que je change le corps de la fonction, je dois redémarrer le REPL.
pkaleta

Vous devez avoir un autre problème dans votre configuration ... :reloadou les :reload-alldeux devraient fonctionner.
Jason

Réponses:


196

Ou (use 'your.namespace :reload)


3
:reload-alldevrait également fonctionner. L'OP dit spécifiquement que non, mais je pense qu'il y avait autre chose qui n'allait pas dans l'environnement de développement de l'OP parce que pour un seul fichier, les deux ( :reloadet :reload-all) devraient avoir le même effet. Voici la commande complète pour :reload-all: (use 'your.namespace :reload-all) Cela recharge également toutes les dépendances.
Jason

77

Il existe également une alternative comme utiliser tools.namespace , c'est assez efficace:

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

user=> (refresh)

:reloading (namespace.app)

:ok

3
cette réponse est plus appropriée
Bahadir Cambel

12
Attention: l'exécution (refresh)semble également faire oublier à la REPL que vous en avez besoin clojure.tools.namespace.repl. Les appels suivants à (refresh)vous donneront une exception RuntimeException, "Impossible de résoudre le symbole: actualiser dans ce contexte." Probablement la meilleure chose à faire est soit (require 'your.namespace :reload-all), ou, si vous savez que vous allez vouloir rafraîchir REPL beaucoup pour un projet donné, faire un :devprofil et ajouter [clojure.tools.namespace.repl :refer (refresh refresh-all)]àdev/user.clj .
Dave Yarwood

1
Article de blog sur le flux de travail Clojure par l'auteur de tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer

61

Le rechargement du code Clojure en utilisant (require … :reload)et :reload-allest très problématique :

  • Si vous modifiez deux espaces de noms qui dépendent l'un de l'autre, vous devez vous rappeler de les recharger dans le bon ordre pour éviter les erreurs de compilation.

  • Si vous supprimez des définitions d'un fichier source et que vous le rechargez, ces définitions sont toujours disponibles en mémoire. Si un autre code dépend de ces définitions, il continuera à fonctionner mais se cassera la prochaine fois que vous redémarrerez la JVM.

  • Si l'espace de noms rechargé contient defmulti, vous devez également recharger toutes les defmethodexpressions associées .

  • Si l'espace de noms rechargé contient defprotocol, vous devez également recharger tous les enregistrements ou types implémentant ce protocole et remplacer toutes les instances existantes de ces enregistrements / types par de nouvelles instances.

  • Si l'espace de noms rechargé contient des macros, vous devez également recharger tous les espaces de noms qui utilisent ces macros.

  • Si le programme en cours d'exécution contient des fonctions qui ferment les valeurs dans l'espace de noms rechargé, ces valeurs fermées ne sont pas mises à jour. (Ceci est courant dans les applications Web qui construisent la «pile de gestionnaires» comme une composition de fonctions.)

La bibliothèque clojure.tools.namespace améliore considérablement la situation. Il fournit une fonction d'actualisation facile qui effectue un rechargement intelligent basé sur un graphique de dépendance des espaces de noms.

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

Malheureusement, un deuxième rechargement échouera si l'espace de noms dans lequel vous avez référencé la refreshfonction a changé. Cela est dû au fait que tools.namespace détruit la version actuelle de l'espace de noms avant de charger le nouveau code.

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)

Vous pouvez utiliser le nom var complet comme solution de contournement à ce problème, mais personnellement, je préfère ne pas avoir à le taper à chaque actualisation. Un autre problème avec ce qui précède est qu'après le rechargement de l'espace de noms principal, les fonctions d'assistance REPL standard (comme docet source) n'y sont plus référencées.

Pour résoudre ces problèmes, je préfère créer un fichier source réel pour l'espace de noms utilisateur afin qu'il puisse être rechargé de manière fiable. J'ai mis le fichier source ~/.lein/src/user.cljmais vous pouvez le placer n'importe où. Le fichier doit nécessiter la fonction de rafraîchissement dans la déclaration ns supérieure comme ceci:

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

Vous pouvez configurer un profil utilisateur leiningen~/.lein/profiles.clj pour que l'emplacement dans lequel vous placez le fichier soit ajouté au chemin de classe. Le profil doit ressembler à ceci:

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

Notez que j'ai défini l'espace de noms utilisateur comme point d'entrée lors du lancement du REPL. Cela garantit que les fonctions d'assistance REPL sont référencées dans l'espace de noms utilisateur au lieu de l'espace de noms principal de votre application. De cette façon, ils ne se perdront que si vous modifiez le fichier source que nous venons de créer.

J'espère que cela t'aides!


Bonnes suggestions. Une question: pourquoi l'entrée ": source-chemins" ci-dessus?
Alan Thompson

2
@DirkGeurs, avec :source-pathsje reçois #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, alors qu'avec :resource-pathstout va bien.
fl00r

1
@ fl00r et il jette toujours cette erreur? Avez-vous un project.clj valide dans le dossier à partir duquel vous lancez le REPL? Cela pourrait résoudre votre problème.
Dirk Geurs

1
Oui, c'est assez standard, et tout fonctionne bien avec :resource-paths, je suis dans mon espace de noms d'utilisateur à l'intérieur de repl.
fl00r

1
J'ai juste passé un bon moment à travailler avec un REPL qui me mentait à cause de ce reloadproblème. Ensuite, il s'est avéré que tout ce que je pensais fonctionner ne fonctionnait plus. Peut-être que quelqu'un devrait régler cette situation?
Alper

41

La meilleure réponse est:

(require 'my.namespace :reload-all)

Cela rechargera non seulement votre espace de noms spécifié, mais rechargera également tous les espaces de noms de dépendances.

Documentation:

exiger


2
C'est la seule réponse qui a fonctionné avec lein repl, Coljure 1.7.0 et nREPL 0.3.5. Si vous êtes nouveau dans clojure: L'espace de noms ( 'my.namespace) est défini avec (ns ...)in src/... /core.clj, par exemple.
Aaron Digulla

1
Le problème avec cette réponse est que la question d'origine utilise (load-file ...), pas besoin. Comment peut-elle ajouter le: reload-all à l'espace de noms après le fichier de chargement?
jgomo3

Comme la structure de l'espace de noms proj.stuff.corereflète la structure des fichiers sur le disque src/proj/stuff/core.clj, le REPL peut localiser le fichier correct et vous n'en avez pas besoin load-file.
Alan Thompson

6

Une doublure basée sur la réponse de Papachan:

(clojure.tools.namespace.repl/refresh)

5

J'utilise ceci dans Lighttable (et l'impressionnant instarepl) mais cela devrait être utile dans d'autres outils de développement. J'avais le même problème avec les anciennes définitions de fonctions et de méthodes multiples qui traînaient après les recharges, alors maintenant pendant le développement au lieu de déclarer des espaces de noms avec:

(ns my.namespace)

Je déclare mes espaces de noms comme ceci:

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

Assez moche mais chaque fois que je réévalue tout l'espace de noms (Cmd-Shift-Enter dans Lighttable pour obtenir les nouveaux résultats instarepl de chaque expression), cela supprime toutes les anciennes définitions et me donne un environnement propre. J'ai été trébuché tous les quelques jours par d'anciennes définitions avant de commencer à faire cela et cela m'a sauvé la raison. :)


3

Essayez à nouveau de charger le fichier?

Si vous utilisez un IDE, il existe généralement un raccourci clavier pour envoyer un bloc de code au REPL, redéfinissant ainsi efficacement les fonctions associées.


1

Dès que cela (use 'foo.bar)fonctionne pour vous, cela signifie que vous avez foo / bar.clj ou foo / bar_init.class sur votre CLASSPATH. Le bar_init.class serait une version compilée AOT de bar.clj. Si vous le faites (use 'foo.bar), je ne sais pas exactement si Clojure préfère la classe à clj ou l'inverse. S'il préfère les fichiers de classe et que vous avez les deux fichiers, il est clair que la modification du fichier clj puis le rechargement de l'espace de noms n'ont aucun effet.

BTW: Vous n'avez pas besoin de le faire load-fileavant le usesi votre CLASSPATH est correctement réglé.

BTW2: Si vous avez besoin d'utiliser load-filepour une raison, vous pouvez simplement le refaire si vous avez modifié le fichier.


14
Je ne sais pas pourquoi cela est marqué comme la bonne réponse. Cela ne répond pas clairement à la question.
AnnanFay

5
En tant que personne qui vient à cette question, je ne trouve pas cette réponse très claire.
ctford
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.