【问题标题】:How to reload a clojure file in REPL如何在 REPL 中重新加载 clojure 文件
【发布时间】:2011-12-01 07:40:11
【问题描述】:

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

  • 编辑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) 一直对我很好。此外,如果您的类路径设置正确,则永远不需要(load-file)。你没有得到什么“必要的效果”?
  • 是的,“需要的效果”是什么?发布示例bar.clj 详细说明“所需效果”。
  • 按要求的效果我的意思是,如果我有一个函数(defn f [] 1) 并将其定义更改为(defn f [] 2),在我看来,在我发出(use 'foo.bar :reload-all) 并调用f 函数之后它应该返回 2,而不是 1。不幸的是,它对我不起作用,每次我更改函数体时,我都必须重新启动 REPL。
  • 您的设置中一定还有其他问题...:reload:reload-all 应该都可以工作。

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


【解决方案1】:

或者 (use 'your.namespace :reload)

【讨论】:

  • :reload-all 也应该可以工作。 OP 明确表示它没有,但我认为 OP 的开发环境中还有其他问题,因为对于单个文件,这两个文件(:reload:reload-all)应该具有相同的效果。这是:reload-all 的完整命令:(use 'your.namespace :reload-all) 这也会重新加载所有依赖项。
【解决方案2】:

还有一个替代方法,比如使用tools.namespace,它非常高效:

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

user=> (refresh)

:reloading (namespace.app)

:ok

【讨论】:

【解决方案3】:

使用(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,但你可以放在任何地方。该文件应该在顶部 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 帮助函数在用户命名空间而不是应用程序的主命名空间中被引用。这样它们就不会丢失,除非您更改我们刚刚创建的源文件。

希望这会有所帮助!

【讨论】:

  • 好建议。一个问题:为什么上面的“:source-paths”条目?
  • @DirkGeurs,:source-paths 我得到#<FileNotFoundException java.io.FileNotFoundException: Could not locate user__init.class or user.clj on classpath: >,而:resource-paths 一切正常。
  • @fl00r 仍然会抛出该错误?启动 REPL 的文件夹中是否有有效的 project.clj?这可能会解决您的问题。
  • 是的,它非常标准,并且在 :resource-paths 下一切正常,我在 repl 内的用户命名空间中。
  • 由于reload 问题,我刚刚与一个对我撒谎的 REPL 度过了愉快的时光。然后事实证明,我认为正常工作的一切都不再存在了。也许有人应该解决这种情况?
【解决方案4】:

最好的答案是:

(require 'my.namespace :reload-all)

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

文档:

require

【讨论】:

  • 这是唯一适用于lein repl、Coljure 1.7.0 和 nREPL 0.3.5 的答案。如果您是 clojure 的新手:例如,命名空间 ('my.namespace) 使用 (ns ...)src/.../core.clj 中定义。
  • 这个答案的问题是原来的问题是使用 (load-file ...),不需要。她如何在加载文件之后将 :reload-all 添加到命名空间?
  • 因为proj.stuff.core这样的命名空间结构反映了src/proj/stuff/core.clj这样的磁盘上的文件结构,所以REPL可以找到正确的文件,你不需要load-file
【解决方案5】:

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

(clojure.tools.namespace.repl/refresh)

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      再次尝试加载文件?

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

      【讨论】:

        【解决方案8】:

        只要(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,那么您可以在编辑文件后再次使用它。

        【讨论】:

        • 不确定为什么这被标记为正确答案。它没有清楚地回答问题。
        • 作为一个提出这个问题的人,我觉得这个答案不是很清楚。
        猜你喜欢
        • 2013-09-04
        • 1970-01-01
        • 2013-02-01
        • 1970-01-01
        • 2012-04-06
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        • 2020-10-08
        相关资源
        最近更新 更多