【问题标题】:What's the difference between Cake and Leiningen?蛋糕和莱宁根有什么区别?
【发布时间】:2011-04-23 19:20:15
【问题描述】:

Cake 和 Leiningen 有什么区别?

【问题讨论】:

标签: clojure leiningen


【解决方案1】:

这个答案继续引起人们的兴趣,大概是作为 StackOverflow 中 Leiningen 的参考,因此现在对其进行了重大编辑以在 2014 年对其进行更新。

Leiningen 和 Cake 早在 2011 年就合并了。Leiningen(第 2 版)现在是事实上的 Clojure 自动化工具。

Leiningen是 Clojure 的构建工具和依赖项管理器,其中包括使用适当配置的类路径以及从 maven 存储库和/或社区以自动方式获取的所有 java 和 clojure 依赖项设置交互式 REPL 的能力基于Clojars

Cake 与 Leiningen 非常相似(当时使用相同的 project.clj 文件格式),但试图通过在后台保持持久 JVM 来避免大量启动开销。在基于 REPL 的迭代开发的典型过程中,由于持久过程中的累积状态(旧的函数定义等),这反应更快,但换取了 bug 的便利。事实证明这是一笔不划算的交易。

在 Leiningen 的经验和对更快启动时间的持续渴望导致了许多加快速度的建议和方法:https://github.com/technomancy/leiningen/wiki/Faster

【讨论】:

    【解决方案2】:

    截至 2011 年 11 月 15 日,公告 cake and lein merge

    【讨论】:

    【解决方案3】:

    正如 Alex 所说,最显着的区别是命令行的速度。 Cake 使用持久的 JVM,因此只有在您第一次在项目中运行任务时才会遇到 jvm 启动开销。如果您不使用 emacs + slime + clojure-test-mode,这可以节省大量时间。例如,我的一个项目的一组相当大的测试在 cake 中运行时间为 0.3 秒,而在 lein 中为 11.2 秒。

    除了性能之外,cake 背后的核心思想是依赖任务模型。每个任务在给定的构建中只运行一次,考虑到依赖图中的所有传递先决条件。这是来自Martin Fowler's article on rake 的蛋糕语法示例,它直接位于您的 project.clj 中。

    (deftask code-gen
      "This task generates code. It has no dependencies."
      (println "generating code...")
      ...)
    
    (deftask compile #{code-gen}
      "This task does the compilation. It depends on code-gen."
      (println "compiling...")
      ...)
    
    (deftask data-load #{code-gen}
      "This task loads the test data. It depends on code-gen."
      (println "loading test data...")
      ...)
    
    (deftask test #{compile data-load}
      "This task runs the tests. It depends on compile and data-load."
      (println "running tests...")
      ...)
    

    要在 Leiningen 中执行相同操作,您首先必须在项目中创建一个 leiningen 目录,其中包含 4 个文件:code_gen.clj、compile.clj、data_load.clj 和 my_test.clj。

    src/leiningen/code_gen.clj

    (ns leiningen.code-gen
       "This task generates code. It has no dependencies.")
    
    (defn code-gen []
      (println "generating code..."))
    

    src/leiningen/my_compile.clj

    (ns leiningen.my-compile
      "This task does the compilation. It depends on code-gen."
      (:use [leiningen.code-gen]))
    
    (defn my-compile []
      (code-gen)
      (println "compiling..."))
    

    src/leiningen/data_load.clj

    (ns leiningen.data-load
      "This task loads the test data. It depends on code-gen."
      (:use [leiningen.code-gen]))
    
    (defn data-load []
      (code-gen)
      (println "loading test data..."))
    

    src/leiningen/my_test.clj

    (ns leiningen.my-test
      "This task runs the tests. It depends on compile and data-load."
      (:use [leiningen.my-compile]
            [leiningen.data-load]))
    
    (defn my-test []
      (my-compile)
      (data-load)
      (println "running tests..."))
    

    人们会期望...

    generating code...
    compiling...
    loading test data...
    running tests...
    

    但是 data-load 和 my-compile 都依赖于 code-gen,所以你的实际输出是......

    generating code...
    compiling...
    generating code...
    loading test data...
    running tests...
    

    你必须记住 code-gen 以防止它被多次运行:

    (ns leiningen.code-gen
       "This task generates code. It has no dependencies.")
    
    (def code-gen (memoize (fn []
                             (println "generating code..."))))
    

    输出:

    generating code...
    compiling...
    loading test data...
    running tests...
    

    这是我们想要的。

    如果每次构建只运行一次任务,构建会更简单、更高效,因此我们将其设为蛋糕构建中的默认行为。这一理念已有数十年的历史,并为一系列构建工具所共有。您仍然可以使用函数,仍然可以重复调用它们,并且始终拥有 clojure 的全部功能。

    Lein 只是为您提供了一个简单的函数作为任务,但附加的约束是它必须在 src 中有自己的命名空间。如果一个任务依赖于它,它将位于一个单独的命名空间中,并且必须在它的 ns 宏中使用/要求另一个。相比之下,Cake 的造型看起来更加整洁简洁。

    另一个关键区别是任务的附加方式。假设我们想添加 my-test 作为 cake/lein 内置的 jar 任务的先决条件。在 cake 中,您可以使用 deftask 宏来附加到任务的表单和依赖项。

    (deftask jar #{my-test})
    

    Lein 使用 Robert Hooke 附加到任务。这是一个非常酷的库,以每个人最喜欢的自然哲学家的名字命名,但它需要一个宏来保持deftask 的简洁性。

    (add-hook #'leiningen.jar/jar (fn [f & args]
                                    (my-test)
                                    (apply f args)))
    

    Cake 还有一个全球项目的概念。您可以将用户特定的开发依赖项(如 swank)添加到 ~/.cake/project.clj 并在您的所有项目中使用它。全局项目还用于在项目之外启动 repl 以进行实验。 Lein 通过支持~/.lein/init.clj 中的每个用户配置和~/.lein/plugins 中的全局插件来实现类似的功能。总的来说,Lein 目前拥有比 cake 更丰富的插件生态系统,但 cake 包含更多开箱即用的任务(war、deploy、java 编译、native dependencies、clojars 和 swank)。 Cljr 也可能值得一试,它本质上只是一个带有包管理器的全球项目,但没有构建功能(但是我没有使用它的经验)。

    正如技术专家指出的那样,真正不可调和的区别在于任务定义。在我(有偏见的)看来,蛋糕处理任务要好得多。当我们开始在 lein 项目中使用协议缓冲区时,对任务依赖模型的需求变得很明显。 Protobufs 是我们所有任务的先决条件,但编译它们真的很慢。我们还有很多相互依赖的任务,所以任何构建都是痛苦的。我也不喜欢我创建的每个任务都需要一个单独的命名空间,因此需要一个额外的 src 文件。开发人员应该创建很多任务,lein 的方法通过创建太多摩擦来阻止这种情况。使用 cake,您可以只使用 project.clj 中的 deftask 宏。

    Cake 还很年轻,还在进行中,但它是一个非常活跃的项目。

    【讨论】:

    • 嗯,还有“lein interactive”,它可以让您运行持久 lein,而无需为每个 lein 命令增加 java 启动时间。
    • 四任务示例相当做作。在 make 中是这样做的,因为 make 没有函数,所以一切都必须是任务。但是你永远不会真的用 Leiningen 那样分解你的任务。这没有任何意义。你永远不会看到这么短的任务;实际上,该任务的命名空间中总是至少有 2 或 3 个支持函数。
    • 对罗伯特胡克的简洁反对也很愚蠢;我用四行代码实现了一个前置宏来完成 Cake 依赖项所做的事情。最困难的部分是为宏选择名称。
    • Dev er dev:从长远来看,持久性并不重要。已经讨论了将持久命令服务器和 repl 添加到 clojure 本身,我们正在努力将其分解到一个单独的库中。 Chas Emerick 的 nREPL 项目也是一个很有前景的解决方案。这是一个非常有用的功能,希望 cake、lein 和任何 clojure 项目有一天都可以使用相同的解决方案。
    • technomancy:当然这是一个人为的例子!没有任务只会打印行 :) 实际上,该示例实际上来自我引用的 MF rake 文章,并且 rake 确实具有功能。该示例并非来自make。关键是,如果您希望函数成为可调用任务并依赖于 lein 中的另一个任务,您必须以这种方式分解您的任务。确实它没有意义,这就是我们创造蛋糕的原因。如果我做错了什么,请使用 lein 发布一个更好的示例,并将可调用任务作为依赖项。这是一个非常有用的要求。
    【解决方案4】:

    主要区别在于任务的实现方式。

    Cake 的方法是“函数定义后很难扩展,所以让我们为任务发明一种新机制而不是使用函数”,这导致了 deftask 宏。

    Leiningen 的方法是“在定义函数后很难扩展它们,因此我们应该找到一种方法来轻松地做到这一点;这样我们就可以将函数用于任务,也能够扩展非任务的东西”,它让您可以将函数的所有可组合性优势应用到任务中。

    【讨论】:

    • 您愿意举个例子吗?我对 Leiningen 或 Cake 都不是很熟悉,我很想看看你所描述的两者之间的区别。
    • 当任务只是函数时,您可以用更有趣的方式扩展它们。见证这个 Leiningen 插件:github.com/technomancy/rodney-leonard-stubbs Cake 的任务扩展只允许附加/前置副作用,而 Leiningen 的允许重新绑定、更改参数/返回值以及条件执行。
    • 蛋糕任务也可以包含函数调用,支持所有这些功能
    猜你喜欢
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-15
    相关资源
    最近更新 更多