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

发布于 2024-12-08 09:51:28 字数 561 浏览 0 评论 0原文

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

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

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

文档:

What is the preferred way of reloading functions defined in a Clojure file without having to restart the REPL. Right now, in order to use the updated file I have to:

  • edit src/foo/bar.clj
  • close the REPL
  • open the REPL
  • (load-file "src/foo/bar.clj")
  • (use 'foo.bar)

In addition, (use 'foo.bar :reload-all) does not result in required effect, which is evaluating the modified bodies of functions and returning new values, instead of behaving as the source haven't changed at all.

Documentation:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

亢潮 2024-12-15 09:51:28

或者
(使用'your.namespace:reload)

Or
(use 'your.namespace :reload)

滿滿的愛 2024-12-15 09:51:28

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

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

user=> (refresh)

:reloading (namespace.app)

:ok

There is also an alternative like using tools.namespace, it's pretty efficient:

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

user=> (refresh)

:reloading (namespace.app)

:ok
尐籹人 2024-12-15 09:51:28

使用 (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.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 辅助函数(如 doc 和 source ) >) 不再在那里引用。

为了解决这些问题,我更喜欢为用户命名空间创建一个实际的源文件,以便可以可靠地重新加载该源文件。 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 辅助函数在用户命名空间而不是应用程序的主命名空间中被引用。这样,除非您更改我们刚刚创建的源文件,否则它们就不会丢失。

希望这有帮助!

Reloading Clojure code using (require … :reload) and :reload-all is very problematic:

  • If you modify two namespaces which depend on each other, you must
    remember to reload them in the correct order to avoid compilation
    errors.

  • If you remove definitions from a source file and then reload it,
    those definitions are still available in memory. If other code
    depends on those definitions, it will continue to work but will
    break the next time you restart the JVM.

  • If the reloaded namespace contains defmulti, you must also reload
    all of the associated defmethod expressions.

  • If the reloaded namespace contains defprotocol, you must also
    reload any records or types implementing that protocol and replace
    any existing instances of those records/types with new instances.

  • If the reloaded namespace contains macros, you must also reload any
    namespaces which use those macros.

  • If the running program contains functions which close over values in
    the reloaded namespace, those closed-over values are not updated.
    (This is common in web applications which construct the "handler
    stack" as a composition of functions.)

The clojure.tools.namespace library improves the situation significantly. It provides an easy refresh function that does smart reloading based on a dependency graph of the namespaces.

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

Unfortunately reloading a second time will fail if the namespace in which you referenced the refresh function changed. This is due to the fact that tools.namespace destroys the current version of the namespace before loading the new code.

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)

You could use the fully qualified var name as a workaround for this problem but personally I prefer not having to type that out on each refresh. Another problem with the above is that after reloading the main namespace the standard REPL helper functions (like doc and source) are no longer referenced there.

To solve these issues I prefer to create an actual source file for the user namespace so that it can be reliably reloaded. I put the source file in ~/.lein/src/user.clj but you can place in anywhere. The file should require the refresh function in the top ns declaration like this:

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

You can setup a leiningen user profile in ~/.lein/profiles.clj so that location you put the file in is added to the class path. The profile should look something like this:

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

Note that I set the user namespace as the entry point when launching the REPL. This ensures that the REPL helper functions get referenced in the user namespace instead of the main namespace of your application. That way they won’t get lost unless you alter the source file we just created.

Hope this helps!

維他命╮ 2024-12-15 09:51:28

最好的答案是:

(require 'my.namespace :reload-all)

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

文档:

需要

The best answer is:

(require 'my.namespace :reload-all)

This will not only reload your specified namespace, but will reload all dependency namespaces as well.

Documentation:

require

口干舌燥 2024-12-15 09:51:28

基于帕帕坎的回答的一条内线:

(clojure.tools.namespace.repl/refresh)

One liner based on papachan's answer:

(clojure.tools.namespace.repl/refresh)
空心空情空意 2024-12-15 09:51:28

我在 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-在Lighttable中输入以获得每个表达式的新instarepl结果),它吹走了所有旧的定义并给了我一个干净的环境。在我开始这样做之前,每隔几天我就会被旧的定义绊倒,它拯救了我的理智。 :)

I use this in Lighttable (and the awesome instarepl) but it should be of use in other development tools. I was having the same problem with old definitions of functions and multimethods hanging around after reloads so now during development instead of declaring namespaces with:

(ns my.namespace)

I declare my namespaces like this:

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

Pretty ugly but whenever I re-evaluate the entire namespace (Cmd-Shift-Enter in Lighttable to get the new instarepl results of each expression), it blows away all old definitions and gives me a clean environment. I was tripped up every few days by old definitions before I started doing this and it has saved my sanity. :)

波浪屿的海角声 2024-12-15 09:51:28

再次尝试加载文件吗?

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

Try load-file again?

If youre using an IDE, there's usually a keyboard shortcut to send a code-block to the REPL, thus effectively re-defining the associated functions.

帅气尐潴 2024-12-15 09:51:28

一旦 (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,那么您可以在编辑文件后再次执行此操作。

As soon as (use 'foo.bar) works for you, it means that you have foo/bar.clj or foo/bar_init.class on your CLASSPATH. The bar_init.class would be an AOT-compiled version of bar.clj. If you do (use 'foo.bar), I'm not exactly sure if Clojure prefers class over clj or the other way round. If it would prefer class files and you have both files, then it's clear that editing the clj file and then reloading the namespace has no effect.

BTW: You don't need to load-file before the use if your CLASSPATH is set properly.

BTW2: If you need to use load-file for a reason, then you can simply do it again if you edited the file.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文