如何在REPL中重新加载clojure文件

时间:2011-10-05 09:09:48

标签: clojure reload read-eval-print-loop leiningen

在不重新启动REPL的情况下,重新加载Clojure文件中定义的函数的首选方法是什么。现在,为了使用更新的文件,我必须:

  • 修改src/foo/bar.clj
  • 关闭REPL
  • 打开REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

此外,(use 'foo.bar :reload-all)不会产生必要的效果,即评估修改的函数体并返回新值,而不是因为源根本没有改变。

8 个答案:

答案 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辅助函数(如docsource)。

要解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载。我将源文件放在~/.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,那么如果您编辑了该文件,则可以再次执行此操作。