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

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

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

此外,(use 'foo.bar :reload-all)不会产生所需的效果,即计算修改后的函数体并返回新值,而不是像源代码一样根本没有改变。

文件:

75561 次浏览

再试试加载文件?

如果您使用 IDE,通常会有一个快捷键将代码块发送给 REPL,从而有效地重新定义相关函数。

一旦 (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 设置正确,你不需要在 use之前设置 load-file

BTW2: 如果您需要使用 load-file的原因,然后您可以简单地再做一次,如果您编辑的文件。

还有一种替代方法,比如使用 名称空间,效率很高:

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


user=> (refresh)


:reloading (namespace.app)


:ok

或者 (use 'your.namespace :reload)

最佳答案是:

(require 'my.namespace :reload-all)

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

文件:

要求

使用 (require … :reload):reload-all重新加载 Clojure 代码是 非常棘手:

  • 如果修改两个相互依赖的命名空间,则必须 请记住以正确的顺序重新加载它们,以避免编译 错误

  • 如果从源文件中删除定义并重新加载它, 这些定义在内存中仍然可用。如果其他代码 取决于这些定义,它将继续工作,但将 下次重新启动 JVM 时中断

  • 如果重新加载的命名空间包含 defmulti,则还必须重新加载 所有相关的 defmethod表达式

  • 如果重新加载的命名空间包含 defprotocol,则还必须 重新加载实现该协议的任何记录或类型并替换 那些带有新实例的记录/类型的任何现有实例。

  • 如果重新加载的命名空间包含宏,则还必须重新加载任何 使用这些宏的命名空间。

  • 如果正在运行的程序包含关闭 重新加载的命名空间,则不更新那些闭合值。 (这在构造“处理程序”的 Web 应用程序中很常见 作为函数的组合。)

名称空间库显著改善了这种情况。它提供了一个简单的刷新函数,可以基于名称空间的依赖关系图进行智能重新加载。

myapp.web=> (require '[clojure.tools.namespace.repl :refer [refresh]])
nil
myapp.web=> (refresh)
:reloading (myapp.web)
:ok

不幸的是,如果引用 refresh函数的命名空间发生更改,则第二次重新加载将失败。这是由于 tools.nampace 在加载新代码之前会销毁名称空间的当前版本。

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]]))

您可以在 ~/.lein/profiles.clj中设置 Leiningen 用户配置文件,以便将文件放入的位置添加到类路径中。个人资料应该是这样的:

{:user {:dependencies [[org.clojure/tools.namespace "0.2.7"]]
:repl-options { :init-ns user }
:source-paths ["/Users/me/.lein/src"]}}

注意,在启动 REPL 时,我将用户名称空间设置为入口点。这样可以确保在用户名称空间而不是应用程序的主名称空间中引用 REPL 助手函数。这样他们就不会丢失除非你改变我们刚刚创建的源文件。

希望这个能帮上忙!

有一句话是基于 Papachan 的回答:

(clojure.tools.namespace.repl/refresh)

我在 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))

相当难看,但是每当我重新评估整个名称空间(cmd-Shift-Enter 在 Lighttable 得到每个表达式的新 instarepl 结果) ,它打破了所有旧的定义,给我一个干净的环境。在我开始这样做之前,我每隔几天就会被旧的定义绊倒,这让我保持了理智。:)