构建和部署Clojure应用的最佳实践:有好的教程吗?

47

我刚接触Clojure,并开始尝试构建一个应用程序。

到目前为止,我看到的所有有关编译Clojure程序的教程都需要交互式操作。例如,“加载REPL并键入(load-file“this-or-that”)运行”。这很好,但还不够。

我习惯于像C或Delphi这样的语言中的编辑-编译-运行惯例,我本能地一边进行编辑,一边按“M-x compile” 。

问题是,“lein uberjar”,据我所知相当于“make”,即使对于hello world也执行速度非常缓慢。因此,我将不得不弄清楚这种“交互式开发”如何工作,并停止像快速制作一样使用uberjar,并仅在一天结束时保存它。

另一件我在使用(使用lein uberjar)过程中注意到的事情是,我正在构建的小GUI应用程序在编译过程中会弹出框架,就像它们在编译时被执行一样。 这似乎与我的想法不太类似;它不完全类似于“make”。

我知道Lisp的开发方式是通过REPL进行交互式工作,我不打算改变这一点:我希望适应这种生活方式。不幸的是,我在如何做到这一点方面看到了很少的文档。例如,如何重置机器的当前状态。只是一直在即时编译单个片段并不能进行某种形式的重置,感觉有点混乱。

我看到的大多数关于Clojure(和Lisp)的教程似乎都专注于在REPL中执行代码。有关应用程序部署的最佳实践对我来说仍然是个谜。我的用户只会是用户;他们不会是将文件加载到REPL中的开发人员。

因此,我的问题是:是否有任何关于构建Clojure应用程序的整个过程(包括部署)的良好信息或教程资源?

(注意:我已经安装并成功使用了所有的前提条件,例如Emacs、Slime、Leiningen等),所以这不是一个关于那方面的问题。

2个回答

26

几个快速提示,然后是一些链接:

在开发过程中不要使用 lein uberjar,而是使用 lein jar。两者的区别在于 lein uberjar 将所有依赖项(包括 Clojure 本身)放入生成的 jar 中,使得您的单个 jar 是一个完全自包含的包,其中包含您的应用程序;lein jar 只打包了你自己的代码。对于部署,uberjar 方法具有明显的优势,但是对于开发来说,您应该能够在运行应用程序时使用适当的类路径,从而节省准备超级 jar 所需的时间。如果您不想手动管理测试运行的类路径,请查看 lein run 插件

另外,大多数情况下,您的大部分代码实际上不应进行 AOT 编译。AOT 在某些 Java 交互方案中是必要的,但大多数情况下,它会带来启动速度的轻微提升以及与不同版本的 Clojure 的二进制兼容性的烦人问题。我想后一个问题与基于 uberjar 的独立应用项目无关,但是任何库代码都应留给 JIT 编译。使用 Leiningen,您可以在 project.clj 中的 defproject 表单中放置一个 :namespaces 子句以确定要编译哪些命名空间;默认情况下会随意留出任何命名空间被 JIT-ed. 旧版本的 Leiningen 默认编译所有内容,这实际上是升级的一个很好的理由!

至于编译时弹出窗口的问题,我猜测您在宏展开期间或在任何函数定义或类似结构之外运行弹出窗口代码(类似顶级的 (println "Foo!"))。除非您打算将代码作为脚本运行,否则您不应该这样做。要避免此问题,请将具有副作用的代码包装在函数定义中,并使用 project.clj 中的 :main 子句提供应用程序的入口点。 (如果您说 :main foo,那么来自 foo 命名空间的 -main 函数将用作您的应用程序的入口点。这是默认值,至少上述提到的 lein run 似乎已经硬编码了名称 - 我不确定 lein 本身如何处理。)

至于重置 REPL 的状态 - 您可以简单地重新启动它。使用 SLIME,M-x slime-restart-inferior-lisp 将仅重新启动 REPL,同时保留 Emacs 会话的所有其他状态。

另请参阅这些 Clojure Google 群的讨论:

  1. Clojure 用于系统管理
  2. 为打包准备Clojure(Re: Clojure 用于系统管理)
  3. Leiningen, Clojure和库:我错过了什么?

感谢提供的信息和链接!lein run插件看起来会对我有所帮助。当然,这又引出了另一个问题:https://dev59.com/mnE95IYBdhLWcg3wPrgI - kes

13

不,您不需要在 REPL 中输入函数。

您像往常一样编辑源文件。Lisp 的优势在于同时在后台运行这个系统,因此您可以从源文件中编译单个函数并将其放入正在运行的系统中,甚至可以在其中替换它们。

如果您使用 Slime,则可以在源文件中按下 C-c C-c 编译和加载指定位置的函数。然后可以切换到 REPL 进行测试和探索,但是您想要作为源代码持久化的任何内容都应该放入源文件中。

教程通常从在 REPL 上输入文字开始,因为这不需要设置太多条件,但是严肃开发需要集成运行系统和源文件管理。


只是为了说明我的一般工作流程(我使用的是 Common Lisp,但 Clojure 类似):

  • 启动 Emacs
  • M-x slime 启动 Slime、Lisp 系统,并通过 Swank 连接两者
  • ,(命令)load-system foo 将当前项目(必要时编译)加载到图像中
  • C-x b 切换到源缓冲区
  • C-c ~ 使源目录成为 REPL 的当前目录,源包成为当前包

现在,我已经设置好了同时在后台运行的系统。接下来的工作流程是:

  • 更改或添加函数或类定义
  • C-c C-c 编译并将其加载到图像中
  • 切换到 REPL 进行测试
  • 调试

没有重要的编译暂停,因为我从不一次性编译整个程序,只编译单个定义。


2
还有C-c C-z可以从任何Lisp源代码缓冲区切换到REPL缓冲区。总的来说,学会正确地利用SLIME和Paredit是最重要的事情,然后还有许多不太重要但仍然非常有用的东西,例如ido-mode(或类似目的的icicles或anything.el)。 (顺便说一句,谢谢编辑!) - Michał Marczyk
我将史莱姆选择器设置为 C-c s,这样可以提供更多的缓冲区切换选项。 - Svante

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接