首页 文章

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

提问于
浏览
141

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

  • 编辑 src/foo/bar.clj

  • 关闭REPL

  • 打开REPL

  • (load-file "src/foo/bar.clj")

  • (use 'foo.bar)

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

8 回答

  • 3

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

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

    使用 (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 中,但您可以放在任何地方 . 该文件应该在top 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帮助程序函数 . 这样,除非你改变我们刚创建的源文件,否则它们不会丢失 .

    希望这可以帮助!

  • 47

    最好的答案是:

    (require 'my.namespace :reload-all)
    

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

  • 0

    (use 'your.namespace :reload)

  • 170

    我在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结果)时,它会吹走所有旧的定义并给我一个干净的环境 . 在我开始这样做之前,我每隔几天就被旧的定义绊倒,这样可以节省我的理智 . :)

  • 4

    基于papachan答案的一个班轮:

    (clojure.tools.namespace.repl/refresh)
    
  • 30

    再次尝试加载文件?

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

  • 2

    只要 (use 'foo.bar) 适合您,就意味着您的CLASSPATH上有foo / bar.clj或foo / bar_init.class . bar_init.class会是一个AOT编译的bar.clj版本 . 如果您执行 (use 'foo.bar) ,我明确表示编辑clj文件然后重新加载命名空间无效 .

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

    BTW2:如果您出于某种原因需要使用 load-file ,那么如果您编辑了文件,则可以再次执行此操作 .

相关问题