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

pka*_*eta 159 clojure reload leiningen read-eval-print-loop

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

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

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

Min*_*ing 185

要么 (use 'your.namespace :reload)

  • `:reload-all`也应该有效.OP明确表示没有,但我认为OP的开发环境还有其他问题,因为对于单个文件,两个(`:reload`和`:reload-all`)应该具有相同的效果.这是`:reload-all`的完整命令:`(使用'your.namespace:reload-all)`这也重新加载了所有依赖项. (2认同)

pap*_*han 72

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

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

user=> (refresh)

:reloading (namespace.app)

:ok
Run Code Online (Sandbox Code Playgroud)

  • 警告:运行`(刷新)`似乎也会导致REPL忘记你需要`clojure.tools.namespace.repl`.对`(refresh)`的后续调用将为您提供RuntimeException,"无法解析符号:在此上下文中刷新".可能最好的做法是`(要求'your.namespace:reload-all)`,或者,如果你知道你想要为给定项目刷新你的REPL,[制作一个`: dev` profile并将`[clojure.tools.namespace.repl:refer(refresh refresh-all)]`添加到`dev/user.clj`](http://thinkrelevance.com/blog/2013/06/04/ Clojure的工作流程,重新加载). (10认同)
  • 这个答案更合适 (3认同)

Dir*_*urs 56

使用(require … :reload)和重新加载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
Run Code Online (Sandbox Code Playgroud)

不幸的是,如果您引用该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)
Run Code Online (Sandbox Code Playgroud)

您可以使用完全限定的var名称作为此问题的解决方法,但我个人不希望在每次刷新时都输入该名称.上面的另一个问题是在重新加载主命名空间之后,不再在那里引用标准REPL辅助函数(如docsource).

为了解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载它.我把源文件放入,~/.lein/src/user.clj但你可以放在任何地方.该文件应该在top ns声明中需要刷新函数,如下所示:

(ns user
  (:require [clojure.tools.namespace.repl :refer [refresh]]))
Run Code Online (Sandbox Code Playgroud)

您可以设置leiningen用户配置文件,~/.lein/profiles.clj以便将放入文件的位置添加到类路径中.该配置文件应如下所示:

{:user {:dependencies [[org.clojure/tools.namespace “0.2.7”]]
        :repl-options { :init-ns user }
        :source-paths [“/Users/me/.lein/src”]}}
Run Code Online (Sandbox Code Playgroud)

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

希望这可以帮助!

  • @DirkGeurs,使用`:source-paths` 我得到`#<FileNotFoundException java.io.FileNotFoundException: 无法在类路径上找到user__init.class 或user.clj:>`,而使用`:resource-paths` 一切正常。 (2认同)

Ala*_*son 37

最好的答案是:

(require 'my.namespace :reload-all)
Run Code Online (Sandbox Code Playgroud)

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

  • 这是唯一适用于“ lein repl”,Coljure 1.7.0和nREPL 0.3.5的答案。如果您是Clojure的新手,例如,命名空间(`'my.namespace`)是通过`src /`...`/ core.clj`中的`(ns ...)`定义的。 (2认同)

Jie*_* Yi 6

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

(clojure.tools.namespace.repl/refresh)
Run Code Online (Sandbox Code Playgroud)


opt*_*evo 5

我在 Lighttable(和很棒的 instarepl)中使用它,但它应该在其他开发工具中使用。我在重新加载后遗留的函数和多方法的旧定义遇到了同样的问题,所以现在在开发过程中而不是声明命名空间:

(ns my.namespace)
Run Code Online (Sandbox Code Playgroud)

我像这样声明我的命名空间:

(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))
Run Code Online (Sandbox Code Playgroud)

非常丑陋,但是每当我重新评估整个命名空间(在 Lighttable 中使用 Cmd-Shift-Enter 来获取每个表达式的新 instarepl 结果)时,它都会清除所有旧定义并为我提供一个干净的环境。在我开始这样做之前,我每隔几天就会被旧的定义绊倒,它挽救了我的理智。:)