[英]How to reload a clojure file in REPL
或者(use 'your.namespace :reload)
还有一种替代方法,例如使用tools.namespace ,它非常有效:
user=> (use '[clojure.tools.namespace.repl :only (refresh)])
user=> (refresh)
:reloading (namespace.app)
:ok
使用(require … :reload)
和:reload-all
重新加载 Clojure 代码非常有问题:
如果你修改了两个相互依赖的命名空间,你必须记住以正确的顺序重新加载它们以避免编译错误。
如果从源文件中删除定义然后重新加载它,这些定义在内存中仍然可用。 如果其他代码依赖于这些定义,它将继续工作,但会在您下次重新启动 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
但你可以放在任何地方。 该文件应该在顶部 ns 声明中需要刷新函数,如下所示:
(ns user
(:require [clojure.tools.namespace.repl :refer [refresh]]))
您可以在~/.lein/profiles.clj
设置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 辅助函数在用户命名空间而不是应用程序的主命名空间中被引用。 这样它们就不会丢失,除非您更改我们刚刚创建的源文件。
希望这可以帮助!
基于 papachan 的回答的一个班轮:
(clojure.tools.namespace.repl/refresh)
我在 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 结果)时,它都会清除所有旧定义并为我提供一个干净的环境。 在我开始这样做之前,我每隔几天就会被旧的定义绊倒,它挽救了我的理智。 :)
再次尝试加载文件?
如果您使用的是 IDE,通常有一个键盘快捷键可以将代码块发送到 REPL,从而有效地重新定义相关功能。
一旦(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 设置正确,您不需要在use
前load-file
。
BTW2:如果您出于某种原因需要使用load-file
,那么如果您编辑了该文件,则只需再次执行此操作即可。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.