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

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

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

另外, (use 'foo.bar :reload-all)不会产生所需的效果,这是对函数的修改主体进行评估并返回新值,而不是源代码根本没有改变。

或者(use 'your.namespace :reload)

还有一个select就像使用tools.namespace一样 ,效率很高:

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

使用(require … :reload)重新加载Clojure代码和:reload-all是非常有问题的 :

  • 如果您修改了两个相互依赖的名称空间,则必须记住以正确的顺序重新装入它们以避免编译错误。

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

  • 如果重新加载的名称空间包含defmulti ,则还必须重新加载所有关联的defmethodexpression式。

  • 如果重新加载的名称空间包含defprotocol ,则还必须重新加载实现该协议的任何logging或types,并用新实例replace这些logging/types的现有实例。

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

  • 如果正在运行的程序包含重新装入名称空间中的值的函数,那么这些已closures的值将不会更新。 (这在构造“处理程序堆栈”作为函数组合的web应用程序中很常见)。

clojure.tools.namespace库显着改善了这种情况。 它提供了一个简单的刷新function,可以根据名称空间的依赖图进行智能重装。

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

不幸的是,重新加载第二次将失败,如果您引用refreshfunction的名称空间更改。 这是由于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名称作为解决此问题的方法,但是我个人更喜欢不必在每次刷新时input该名称。 上面的另一个问题是,在重新加载主名称空间之后,标准的REPL帮助函数(如docsource )不再被引用。

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

 (ns user (:require [clojure.tools.namespace.repl :refer [refresh]])) 

您可以在~/.lein/profiles.clj设置leiningen用户configuration ~/.lein/profiles.clj以便将文件放入的位置添加到类path中。 该configuration文件应该是这样的:

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

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

希望这可以帮助!

最好的答案是:

 (require 'my.namespace :reload-all) 

这不仅将重新加载您指定的名称空间,而且还将重新加载所有依赖项名称空间。

我在Lighttable(和真棒instarepl)中使用它,但它应该在其他开发工具中使用。 我在旧的函数定义和multimethods问题中重复加载之后仍然存在相同的问题,所以现在在开发过程中不用声明名称空间:

 (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中以获得每个expression式的新instalpl结果)时,它吹走了所有旧的定义,给了我一个干净的环境。 在我开始这样做之前,我每隔几天就被绊倒了,这让我保持了清醒的头脑。 🙂

再次尝试加载文件?

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

一个基于papachan的答案class轮:

 (clojure.tools.namespace.repl/refresh) 

只要(use 'foo.bar)为你工作,这意味着你的CLASSPATH上有foo / bar.clj或foo / bar_init.class。 bar_init.class是bar.clj的AOT编译版本。 如果你这样做(use 'foo.bar) ,我不确定Clojure是否喜欢(use 'foo.bar) 。 如果它更喜欢类文件,并且你有两个文件,那么很明显,编辑clj文件然后重新加载命名空间不起作用。

BTW:如果您的CLASSPATH设置正确,您不需要在use之前load-file

BTW2:如果你需要使用load-file的原因,那么你可以简单地再做一遍,如果你编辑文件。