Como recarregar um arquivo clojure no REPL


170

Qual é a maneira preferida de recarregar funções definidas em um arquivo Clojure sem ter que reiniciar o REPL. Agora, para usar o arquivo atualizado, tenho que:

  • editar src/foo/bar.clj
  • feche o REPL
  • abra o REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

Além disso, (use 'foo.bar :reload-all)não resulta no efeito requerido, que é avaliar os corpos modificados das funções e retornar novos valores, em vez de se comportar como a fonte não mudou.

Documentação:


20
(use 'foo.bar :reload-all)sempre funcionou bem para mim. Além disso, (load-file)nunca deve ser necessário se você tiver o caminho de classe configurado corretamente. Qual é o "efeito necessário" que você não está obtendo?
Dave Ray

Sim, qual é o "efeito necessário"? Poste uma amostra bar.cljdetalhando o "efeito necessário".
Sridhar Ratnakumar

1
Por efeito requerido, quis dizer que, se eu tivesse uma função (defn f [] 1)e alterasse sua definição para (defn f [] 2), parecia-me que, depois de emitir (use 'foo.bar :reload-all)e chamar a ffunção, ela retornaria 2, não 1. Infelizmente, não funciona dessa maneira para mim e para todos os envolvidos. Quando mudo o corpo da função, tenho que reiniciar o REPL.
Pkaleta 5/10

Você deve ter outro problema na sua configuração ... :reloadou :reload-allambos devem funcionar.
19416 Jason

Respostas:


196

Ou (use 'your.namespace :reload)


3
:reload-alltambém deve funcionar. O OP diz especificamente que não, mas acho que havia algo mais errado no ambiente de desenvolvimento do OP, porque, para um único arquivo, os dois ( :reloade :reload-all) deveriam ter o mesmo efeito. Aqui está o comando completo para :reload-all: (use 'your.namespace :reload-all) Isso recarrega todas as dependências também.
19416 Jason

77

Há também uma alternativa, como usar tools.namespace , é bastante eficiente:

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

user=> (refresh)

:reloading (namespace.app)

:ok

3
esta resposta é mais adequada
Bahadir Cambel

12
Advertência: correr (refresh)parece também fazer com que o REPL esqueça que você solicitou clojure.tools.namespace.repl. As chamadas subsequentes para (refresh)fornecerão uma RuntimeException, "Não é possível resolver o símbolo: atualização neste contexto". Provavelmente, a melhor coisa a fazer é (require 'your.namespace :reload-all), ou, se você sabe que deseja atualizar muito o seu REPL para um determinado projeto, faça um :devperfil e adicione [clojure.tools.namespace.repl :refer (refresh refresh-all)]-odev/user.clj .
Dave Yarwood 31 /

1
Blogpost no fluxo de trabalho Clojure pelo autor do tools.namespace: thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer

61

Recarregar o código Clojure usando (require … :reload)e :reload-allé muito problemático :

  • Se você modificar dois espaços para nome que dependem um do outro, lembre-se de recarregá-los na ordem correta para evitar erros de compilação.

  • Se você remover definições de um arquivo de origem e recarregá-lo, essas definições ainda estarão disponíveis na memória. Se outro código depender dessas definições, ele continuará funcionando, mas será interrompido na próxima vez que você reiniciar a JVM.

  • Se o espaço para nome recarregado contiver defmulti, você também deverá recarregar todas as defmethodexpressões associadas .

  • Se o espaço para nome recarregado contiver defprotocol, você também deverá recarregar todos os registros ou tipos que implementam esse protocolo e substituir quaisquer instâncias existentes desses registros / tipos por novas instâncias.

  • Se o espaço para nome recarregado contiver macros, você também deverá recarregar todos os espaços para nome que usem essas macros.

  • Se o programa em execução contiver funções que fechem sobre os valores no espaço para nome recarregado, esses valores fechados não serão atualizados. (Isso é comum em aplicativos da Web que constroem a "pilha do manipulador" como uma composição de funções).

A biblioteca clojure.tools.namespace melhora significativamente a situação. Ele fornece uma função de atualização fácil que faz o recarregamento inteligente com base em um gráfico de dependência dos namespaces.

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

Infelizmente, recarregar uma segunda vez falhará se o espaço de nomes no qual você referenciou a refreshfunção foi alterado. Isso ocorre porque o tools.namespace destrói a versão atual do namespace antes de carregar o novo 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)

Você pode usar o nome completo do var como solução alternativa para esse problema, mas pessoalmente prefiro não precisar digitar isso a cada atualização. Outro problema com o exposto acima é que, após recarregar o namespace principal, as funções auxiliares do REPL padrão (como doce source) não são mais referenciadas lá.

Para resolver esses problemas, prefiro criar um arquivo de origem real para o espaço de nome do usuário, para que ele possa ser recarregado com segurança. Coloquei o arquivo de origem, ~/.lein/src/user.cljmas você pode colocá-lo em qualquer lugar. O arquivo deve exigir a função de atualização na declaração ns superior como esta:

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

Você pode configurar um perfil de usuário leiningen~/.lein/profiles.clj para que o local em que você colocou o arquivo seja adicionado ao caminho da classe. O perfil deve ser algo como isto:

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

Observe que eu defino o espaço para nome do usuário como ponto de entrada ao iniciar o REPL. Isso garante que as funções auxiliares do REPL sejam referenciadas no espaço para nome do usuário em vez do espaço para nome principal do seu aplicativo. Dessa forma, eles não se perderão, a menos que você altere o arquivo de origem que acabamos de criar.

Espero que isto ajude!


Boas sugestões. Uma pergunta: por que a entrada ": source-caminhos" acima?
Alan Thompson

2
@DirkGeurs, com :source-pathseu recebo #<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >, enquanto :resource-pathstudo está bem.
fl00r

1
@ fl00r e ainda gera esse erro? Você tem um project.clj válido na pasta da qual você está iniciando o REPL? Isso pode resolver o seu problema.
Dirk Geurs

1
Sim, é bastante padrão, e tudo funciona bem :resource-paths, eu estou no meu espaço para nome de usuário dentro de repl.
fl00r

1
Eu me diverti muito trabalhando com um REPL que estava mentindo para mim por causa desse reloadproblema. Depois, tudo o que pensei que estava funcionando não estava mais. Talvez alguém deva resolver esta situação?
Alper

41

A melhor resposta é:

(require 'my.namespace :reload-all)

Isso não apenas recarregará seu espaço de nomes especificado, mas também todos os espaços de nomes de dependência.

Documentação:

exigir


2
Esta é a única resposta que trabalhou com o lein replColjure 1.7.0 e o nREPL 0.3.5. Se você é novo no clojure: O espaço para nome ( 'my.namespace) é definido com (ns ...)em src/... /core.clj, por exemplo.
Aaron Digulla

1
O problema com esta resposta é que a pergunta original está usando (carregar arquivo ...), sem necessidade. Como ela pode adicionar o: reload-all ao namespace após o arquivo de carregamento?
Jgomo3

Como a estrutura do namespace proj.stuff.corereflete a estrutura do arquivo no disco src/proj/stuff/core.clj, o REPL pode localizar o arquivo correto e você não precisa load-file.
Alan Thompson

6

Um forro baseado na resposta do papachan:

(clojure.tools.namespace.repl/refresh)

5

Eu uso isso no Lighttable (e no impressionante instarepl), mas deve ser usado em outras ferramentas de desenvolvimento. Eu estava tendo o mesmo problema com definições antigas de funções e métodos múltiplos, depois de recarregadas, agora durante o desenvolvimento, em vez de declarar namespaces com:

(ns my.namespace)

Declaro meus namespaces assim:

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

Muito feio, mas sempre que reavaliamos todo o espaço para nome (Cmd-Shift-Enter no Lighttable para obter os novos resultados instarepl de cada expressão), ele afasta todas as definições antigas e cria um ambiente limpo. Fui enganado todos os dias por definições antigas antes de começar a fazer isso e isso salvou minha sanidade. :)


3

Tente carregar o arquivo novamente?

Se você estiver usando um IDE, geralmente há um atalho de teclado para enviar um bloco de código ao REPL, redefinindo efetivamente as funções associadas.


1

Assim que (use 'foo.bar)funciona para você, significa que você tem foo / bar.clj ou foo / bar_init.class no seu CLASSPATH. A barra_init.class seria uma versão compilada pela AOT do bar.clj. Se o fizer (use 'foo.bar), não sei exatamente se Clojure prefere a classe ao invés de clj ou o contrário. Se preferir arquivos de classe e você tiver os dois, fica claro que editar o arquivo clj e recarregar o espaço para nome não tem efeito.

BTW: Você não precisa load-fileantes do usese o seu CLASSPATH estiver definido corretamente.

BTW2: Se você precisar usá-lo load-filepor algum motivo, poderá fazê-lo novamente se tiver editado o arquivo.


14
Não sei por que isso está marcado como a resposta correta. Não responde a pergunta claramente.
AnnanFay

5
Como alguém que vem a esta pergunta, não acho essa resposta muito clara.
Ctford # 5/13
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.