使用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帮助器函数(如doc
和source
)。
为了解决这些问题,我更喜欢为用户名称空间创建一个实际的源文件,以便可以可靠地重新加载它。我将源文件放入其中,~/.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帮助程序函数在用户名称空间而不是应用程序的主名称空间中被引用。这样,除非您更改我们刚刚创建的源文件,否则它们不会丢失。
希望这可以帮助!
(use 'foo.bar :reload-all)
一直对我很好。另外,(load-file)
如果您正确设置了类路径,则永远不需要。您没有得到的“所需效果”是什么?