使用(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,但你可以放在任何地方。该文件应该在顶部 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 帮助函数在用户命名空间而不是应用程序的主命名空间中被引用。这样它们就不会丢失,除非您更改我们刚刚创建的源文件。
希望这会有所帮助!