在不重新启动REPL的情况下,重新加载Clojure文件中定义的函数的首选方法是什么。现在,为了使用更新的文件,我必须:
src/foo/bar.clj
(load-file "src/foo/bar.clj")
(use 'foo.bar)
此外,(use 'foo.bar :reload-all)
不会产生必要的效果,即评估修改的函数体并返回新值,而不是因为源根本没有改变。
答案 0 :(得分:180)
或者
(use 'your.namespace :reload)
答案 1 :(得分:70)
还有一种替代方法,比如使用tools.namespace,它非常有效:
user=> (use '[clojure.tools.namespace.repl :only (refresh)])
user=> (refresh)
:reloading (namespace.app)
:ok
答案 2 :(得分:53)
使用(require … :reload)
和:reload-all
重新加载Clojure代码为very problematic:
如果修改两个相互依赖的命名空间,则必须这样做 记得以正确的顺序重新加载它们以避免编译 错误。
如果从源文件中删除定义然后重新加载, 这些定义仍然可以在内存中找到。如果是其他代码 取决于这些定义,它将继续工作,但会 下次重新启动JVM时中断。
如果重新加载的命名空间包含
defmulti
,则还必须重新加载 所有相关的defmethod
表达式。如果重新加载的命名空间包含
defprotocol
,您还必须 重新加载实现该协议和替换的任何记录或类型 具有新实例的那些记录/类型的任何现有实例。如果重新加载的命名空间包含宏,则还必须重新加载任何宏 使用这些宏的名称空间。
如果正在运行的程序包含关闭值的函数 重新加载的命名空间,这些已关闭的值不会更新。 (这在构建“处理程序”的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辅助函数(如doc
和source
)。
要解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载。我将源文件放在~/.lein/src/user.clj
中,但您可以放在任何地方。该文件应该在top ns声明中需要刷新功能,如下所示:
(ns user
(:require [clojure.tools.namespace.repl :refer [refresh]]))
您可以在~/.lein/profiles.clj
中设置a leiningen user profile,以便将放入文件的位置添加到类路径中。该配置文件应如下所示:
{:user {:dependencies [[org.clojure/tools.namespace “0.2.7”]]
:repl-options { :init-ns user }
:source-paths [“/Users/me/.lein/src”]}}
请注意,我在启动REPL时将用户命名空间设置为入口点。这可确保在用户命名空间而不是应用程序的主命名空间中引用REPL帮助程序函数。这样他们就不会迷失,除非你改变我们刚创建的源文件。
希望这有帮助!
答案 3 :(得分:34)
最好的答案是:
(require 'my.namespace :reload-all)
这不仅会重新加载指定的命名空间,还会重新加载所有依赖项命名空间。
答案 4 :(得分:4)
基于papachan的答案:
(clojure.tools.namespace.repl/refresh)
答案 5 :(得分:4)
我在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结果)时,它就会吹掉所有旧的定义并给我一个干净的环境。在我开始这样做之前,我每隔几天就被旧的定义绊倒,这样可以节省我的理智。 :)
答案 6 :(得分:2)
再次尝试加载文件?
如果您使用的是IDE,通常会有一个键盘快捷键来向EPL发送代码块,这有效地重新定义了相关的功能。
答案 7 :(得分:0)
只要(use 'foo.bar)
适合您,就意味着您的CLASSPATH上有foo / bar.clj或foo / bar_init.class。 bar_init.class是bar.clj的AOT编译版本。如果你做(use 'foo.bar)
,我不确定Clojure是喜欢上课还是反过来。如果它更喜欢类文件并且你有两个文件,那么很明显编辑clj文件然后重新加载命名空间没有任何效果。
BTW:如果正确设置了CLASSPATH,则load-file
之前不需要use
。
BTW2:如果您出于某种原因需要使用load-file
,那么如果您编辑了该文件,则可以再次执行此操作。