185

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

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

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

文档:

4

8 回答 8

217

或者 (use 'your.namespace :reload)

于 2013-12-02T21:12:26.867 回答
80

还有一种替代方法,例如使用tools.namespace,它非常有效:

user=> (use '[clojure.tools.namespace.repl :only (refresh)])

user=> (refresh)

:reloading (namespace.app)

:ok
于 2013-04-12T12:50:34.710 回答
69

(require … :reload)使用and重新加载 Clojure 代码: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 在加载新代码之前破坏了当前版本的命名空间。

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但你可以放在任何地方。该文件应该需要顶部 ns 声明中的刷新函数,如下所示:

(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 帮助函数在用户命名空间而不是应用程序的主命名空间中被引用。这样,除非您更改我们刚刚创建的源文件,否则它们不会丢失。

希望这可以帮助!

于 2014-09-22T17:16:54.727 回答
51

最佳答案是:

(require 'my.namespace :reload-all)

这不仅会重新加载您指定的命名空间,还会重新加载所有依赖项命名空间。

文档:

要求

于 2014-08-03T06:29:35.980 回答
6

基于papachan的回答的一个班轮:

(clojure.tools.namespace.repl/refresh)
于 2015-04-24T20:35:03.797 回答
5

我在 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 结果)时,它都会清除所有旧定义并给我一个干净的环境。在我开始这样做之前,我每隔几天就会被旧定义绊倒,它挽救了我的理智。:)

于 2015-12-02T10:53:03.997 回答
3

再次尝试加载文件?

如果您使用的是 IDE,通常有一个键盘快捷键可以将代码块发送到 REPL,从而有效地重新定义相关功能。

于 2011-10-05T12:05:01.633 回答
1

只要(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 设置正确,则不需要在load-file之前。use

BTW2:如果您出于某种原因需要使用load-file,那么您可以在编辑文件后再次使用它。

于 2012-02-02T20:35:26.123 回答