重新加载Clojure文件中定义的函数的首选方法是什么,而不必重新启动REPL . 现在,为了使用更新的文件,我必须:
编辑 src/foo/bar.clj
src/foo/bar.clj
关闭REPL
打开REPL
(load-file "src/foo/bar.clj")
(use 'foo.bar)
此外, (use 'foo.bar :reload-all) 不会产生所需的效果,即评估修改的函数体并返回新值,而不是因为源根本没有改变 .
(use 'foo.bar :reload-all)
还有一种替代方法,比如使用tools.namespace,效率非常高:
user=> (use '[clojure.tools.namespace.repl :only (refresh)]) user=> (refresh) :reloading (namespace.app) :ok
使用 (require … :reload) 和 :reload-all 重新加载Clojure代码是very problematic:
(require … :reload)
:reload-all
如果修改两个相互依赖的名称空间,则必须记住以正确的顺序重新加载它们以避免编译错误 . 如果从源文件中删除定义然后重新加载它们,那么这些定义仍可在内存中使用 . 如果其他代码依赖于这些定义,它将继续工作,但下次重新启动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在加载新代码之前会破坏当前版本的命名空间 .
refresh
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 ) .
doc
source
为了解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载它 . 我将源文件放在 ~/.lein/src/user.clj 中,但您可以放在任何地方 . 该文件应该在top ns声明中需要刷新函数,如下所示:
~/.lein/src/user.clj
(ns user (:require [clojure.tools.namespace.repl :refer [refresh]]))
您可以在 ~/.lein/profiles.clj 中设置a leiningen user profile,以便将放入文件的位置添加到类路径中 . 该配置文件应如下所示:
~/.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帮助程序函数 . 这样,除非你改变我们刚创建的源文件,否则它们不会丢失 .
希望这可以帮助!
最好的答案是:
(require 'my.namespace :reload-all)
这不仅会重新加载您指定的命名空间,还会重新加载所有依赖项命名空间 .
或 (use 'your.namespace :reload)
(use 'your.namespace :reload)
我在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结果)时,它会吹走所有旧的定义并给我一个干净的环境 . 在我开始这样做之前,我每隔几天就被旧的定义绊倒,这样可以节省我的理智 . :)
基于papachan答案的一个班轮:
(clojure.tools.namespace.repl/refresh)
再次尝试加载文件?
如果您使用的是IDE,通常会有一个键盘快捷键将代码块发送到REPL,从而有效地重新定义相关的功能 .
只要 (use 'foo.bar) 适合您,就意味着您的CLASSPATH上有foo / bar.clj或foo / bar_init.class . bar_init.class会是一个AOT编译的bar.clj版本 . 如果您执行 (use 'foo.bar) ,我明确表示编辑clj文件然后重新加载命名空间无效 .
BTW:如果正确设置了CLASSPATH,则在 use 之前不需要 load-file .
use
load-file
BTW2:如果您出于某种原因需要使用 load-file ,那么如果您编辑了文件,则可以再次执行此操作 .
8 回答
还有一种替代方法,比如使用tools.namespace,效率非常高:
使用
(require … :reload)
和:reload-all
重新加载Clojure代码是very problematic:clojure.tools.namespace库可以显着改善这种情况 . 它提供了一个简单的刷新功能,可以根据命名空间的依赖关系图进行智能重新加载 .
不幸的是,如果您引用
refresh
函数的命名空间发生了更改,则第二次重新加载将失败 . 这是因为tools.namespace在加载新代码之前会破坏当前版本的命名空间 .您可以使用完全限定的var名称作为此问题的解决方法,但我个人不希望在每次刷新时都输入该名称 . 上面的另一个问题是在重新加载主命名空间之后,不再引用标准的REPL辅助函数(如
doc
和source
) .为了解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载它 . 我将源文件放在
~/.lein/src/user.clj
中,但您可以放在任何地方 . 该文件应该在top ns声明中需要刷新函数,如下所示:您可以在
~/.lein/profiles.clj
中设置a leiningen user profile,以便将放入文件的位置添加到类路径中 . 该配置文件应如下所示:请注意,我在启动REPL时将用户命名空间设置为入口点 . 这可确保在用户命名空间而不是应用程序的主命名空间中引用REPL帮助程序函数 . 这样,除非你改变我们刚创建的源文件,否则它们不会丢失 .
希望这可以帮助!
最好的答案是:
这不仅会重新加载您指定的命名空间,还会重新加载所有依赖项命名空间 .
或
(use 'your.namespace :reload)
我在Lighttable(和令人敬畏的instarepl)中使用它,但它应该在其他开发工具中使用 . 在重新加载后,我遇到了旧的函数定义和多方法的问题,所以现在在开发期间,而不是使用以下命名声明名称空间:
我声明我的命名空间如下:
非常难看,但每当我重新评估整个命名空间(在Lighttable中使用Cmd-Shift-Enter来获取每个表达式的新instarepl结果)时,它会吹走所有旧的定义并给我一个干净的环境 . 在我开始这样做之前,我每隔几天就被旧的定义绊倒,这样可以节省我的理智 . :)
基于papachan答案的一个班轮:
再次尝试加载文件?
如果您使用的是IDE,通常会有一个键盘快捷键将代码块发送到REPL,从而有效地重新定义相关的功能 .
只要
(use 'foo.bar)
适合您,就意味着您的CLASSPATH上有foo / bar.clj或foo / bar_init.class . bar_init.class会是一个AOT编译的bar.clj版本 . 如果您执行(use 'foo.bar)
,我明确表示编辑clj文件然后重新加载命名空间无效 .BTW:如果正确设置了CLASSPATH,则在
use
之前不需要load-file
.BTW2:如果您出于某种原因需要使用
load-file
,那么如果您编辑了文件,则可以再次执行此操作 .