如何在REPL中重新加载Clojure文件


170

重新加载Clojure文件中定义的功能而不必重新启动REPL的首选方法是什么。现在,为了使用更新的文件,我必须:

  • 编辑 src/foo/bar.clj
  • 关闭REPL
  • 打开REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

此外,(use 'foo.bar :reload-all)不会产生所需的效果,即评估函数的修改主体并返回新值,而不是表现为源未发生任何变化。

说明文件:


20
(use 'foo.bar :reload-all)一直对我很好。另外,(load-file)如果您正确设置了类路径,则永远不需要。您没有得到的“所需效果”是什么?
戴夫·雷

是的,什么是“所需的效果”?发布bar.clj有关“所需效果”的详细样本。
2011年

1
所谓有需要的效果,是指如果我有一个函数,(defn f [] 1)并且将其定义更改为(defn f [] 2),在我看来,在我发出(use 'foo.bar :reload-all)并调用该f函数后,它应该返回2,而不是1。当我更改功能主体时,必须重新启动REPL。
pkaleta 2011年

您的设置中必须有另一个问题... :reload或者:reload-all两者都可以工作。
杰森

Answers:


196

要么 (use 'your.namespace :reload)


3
:reload-all应该也可以。OP明确表示没有,但是我认为OP的开发环境中还有其他问题,因为对于单个文件,两个(:reload:reload-all)应该具有相同的效果。这是用于的完整命令:reload-all(use 'your.namespace :reload-all) 这也会重新加载所有依赖项。
杰森

77

还有一个替代方法,例如使用tools.namespace,它非常有效:

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

user=> (refresh)

:reloading (namespace.app)

:ok

3
这个答案更合适
Bahadir Cambel 2014年

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
Dave Yarwood'1

1
tools.namespace作者在Clojure工作流程上的博客文章:thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
David Tonhofer

61

使用Clojure的重新加载代码(require … :reload):reload-all非常有问题的

  • 如果您修改了两个相互依赖的命名空间,则必须记住以正确的顺序重新加载它们,以避免编译错误。

  • 如果从源文件中删除定义,然后重新加载它们,则这些定义在内存中仍然可用。如果其他代码取决于这些定义,它将继续工作,但是在下次重新启动JVM时会中断。

  • 如果重新加载的名称空间包含defmulti,则还必须重新加载所有关联的defmethod表达式。

  • 如果重新加载的名称空间包含defprotocol,则您还必须重新加载实现该协议的所有记录或类型,并将这些记录/类型的所有现有实例替换为新实例。

  • 如果重新加载的名称空间包含宏,则还必须重新加载使用这些宏的所有名称空间。

  • 如果正在运行的程序包含关闭重新加载的名称空间中的值的函数,则不会更新这些关闭的值。(这在Web应用程序中很常见,Web应用程序将“处理程序堆栈”构造为功能的组合。)

clojure.tools.namespace库大大改善了这种情况。它提供了一个简单的刷新功能,该功能基于名称空间的依赖关系图进行智能重新加载。

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

不幸的是,如果引用refresh函数的名称空间发生更改,则第二次重新加载将失败。这是由于tools.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帮助器函数(如docsource)。

为了解决这些问题,我更喜欢为用户名称空间创建一个实际的源文件,以便可以可靠地重新加载它。我将源文件放入其中,~/.lein/src/user.clj但是您可以将其放置在任何位置。该文件应在顶部ns声明中要求使用refresh函数,如下所示:

(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 2015年

1
@ fl00r,它仍然会抛出该错误?从中启动REPL的文件夹中是否有有效的project.clj?那可能会解决您的问题。
Dirk Geurs 2015年

1
是的,这是非常标准的,并且都可以正常使用:resource-paths,我在repl中的用户名称空间中。
fl00r 2015年

1
由于这个reload问题,我在与REPL合作的过程中度过了愉快的时光。然后事实证明,我认为正在工作的所有内容都不再可用。也许有人应该解决这种情况?
Alper

41

最好的答案是:

(require 'my.namespace :reload-all)

这不仅将重新加载您指定的名称空间,还将重新加载所有依赖项名称空间。

说明文件:

要求


2
这是与lein replColjure 1.7.0和nREPL 0.3.5 一起使用的唯一答案。如果您不'my.namespace熟悉clojure:例如,名称空间()是使用(ns ...)in src/... 定义的/core.clj
亚伦·迪古拉

1
这个答案的问题是原始问题正在使用(load-file ...),不需要。她如何在加载文件之后将:reload-all添加到名称空间?
jgomo3

因为名称空间结构像proj.stuff.core磁盘上的文件结构一样镜像src/proj/stuff/core.clj,所以REPL可以找到正确的文件,而您不需要load-file
艾伦·汤普森

6

一种基于papachan的答案的班轮:

(clojure.tools.namespace.repl/refresh)

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

很难看,但是每当我重新评估整个命名空间时(在Lighttable中执行Cmd-Shift-Enter以获得每个表达式的新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是否更喜欢class而不是clj或相反。如果它更喜欢类文件,并且您同时拥有这两个文件,那么很显然,编辑clj文件然后重新加载名称空间无效。

顺便说一句:如果您的CLASSPATH设置正确load-fileuse则不需要。

BTW2:如果您load-file出于某种原因需要使用,那么只要编辑了文件,就可以再次使用它。


14
不知道为什么将其标记为正确答案。它不能清楚地回答问题。
AnnanFay 2013年

5
当有人提出这个问题时,我觉得这个答案不太清楚。
ctford 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.