【问题标题】:Setting Clojure "constants" at runtime在运行时设置 Clojure “常量”
【发布时间】:2010-10-22 14:42:07
【问题描述】:

我有一个使用 Maven 构建为 JAR 文件的 Clojure 程序。 JAR Manifest 中嵌入的是构建版本号,包括构建时间戳。

我可以使用以下代码在运行时从 JAR Manifest 轻松读取此内容:

(defn set-version
  "Set the version variable to the build number."
  []
  (def version
    (-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
                                   (.getCodeSource)
                                   (.getLocation))
                    "!/META-INF/MANIFEST.MF")
      (URL.)
      (.openStream)
      (Manifest.)
      (.. getMainAttributes)
      (.getValue "Build-number"))))

但有人告诉我,在 defn 内使用 def 是不好的业力。

在运行时设置常量的 Clojure 惯用方法是什么?我显然没有将构建版本信息作为def 嵌入到我的代码中,但我希望在程序启动时从main 函数中设置一次(并且一劳永逸)。然后它应该以def 的形式提供给其余正在运行的代码。

更新:顺便说一句,Clojure 一定是我很长一段时间以来遇到的最酷的语言之一。向 Rich Hickey 致敬!

【问题讨论】:

    标签: clojure runtime constants


    【解决方案1】:

    我仍然认为最简洁的方法是在应用程序的 main 方法中使用 alter-var-root

    (declare version)
    
    (defn -main
      [& args]
      (alter-var-root #'version (constantly (-> ...)))
      (do-stuff))
    

    它在编译时声明 Var,在运行时设置它的根值一次,不需要 deref 并且不绑定到主线程。您在上一个问题中没有回应此建议。您尝试过这种方法吗?

    【讨论】:

    • 我没试过,但我会的。看起来很有趣。设置值后不会降低性能。我也可以使用这种技术从命令行选项设置值——只需要设置一次。
    【解决方案2】:

    您可以使用动态绑定。

    (declare *version*)
    
    (defn start-my-program []
      (binding [*version* (read-version-from-file)]
        (main))
    

    现在main 及其调用的每个函数都会看到*version* 的值。

    【讨论】:

    • +1 提醒我“及其调用的每个函数”是动态绑定,可能适合问题
    • 我喜欢这个!我打算使用原子,但我需要进一步探索。无论如何,我需要设置的变量(常量)都应该在程序的其余部分运行之前设置。除了版本号之外,我还将使用记录通过 Apache commons-cli 获得的命令行选项的解决方案。
    • 记住binding 的线程边界。另见bound-fn
    • 当前的问题是绑定只适用于创建它们的线程。因此,如果在调用堆栈下方的某个地方使用了线程池(例如,使用 pmap),那么绑定就会丢失——来自 Rich Hickey 在 ClojureConj 上的演讲
    【解决方案3】:

    虽然 kotarak 的解决方案效果很好,但这里有另一种方法:将您的代码转换为返回版本的记忆函数。像这样:

    (def get-version
     (memoize
      (fn []
        (-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
                (.getCodeSource)
                (.getLocation))
             "!/META-INF/MANIFEST.MF")
        (URL.)
        (.openStream)
        (Manifest.)
        (.. getMainAttributes)
        (.getValue "Build-number")))))
    

    【讨论】:

      【解决方案4】:

      我希望这次我不会错过任何东西。

      如果 version 是一个常量,它将被定义一次并且不会被更改,您可以简单地删除 defn 并保持 (def version ... ) 单独。我想你出于某种原因不想要这个。

      如果您想更改 fn 中的全局变量,我认为更惯用的方法是使用一些并发构造来存储数据并以安全的方式访问和更改它 例如:

      (def *version* (atom ""))
      
      (defn set-version! [] (swap! *version* ...))
      

      【讨论】:

      • @jneira:“希望这次我不会错过什么。” :-) 我不能这样做,因为它会在编译时评估(我相信)并且还没有要读取的 JAR 文件。
      • 呵呵 :-P 好的,第二个选项? (第三个是函数返回字符串而不是进行重定义,但对于各种调用可能效率低下)
      • 我在想我需要对原子做点什么,我只是觉得这会有点过分,同步开销等等。也许只是从 main 函数调用 def 会是好吧,即使它“不受欢迎”。
      • 再想一想,读取原子的开销应该很低,而且只会设置一次,所以也许使用原子是最好的方法。跨度>
      • (def version) 将是编译时间。在函数中调用 def 的替代方法是交换! (或者重置!,因为初始值是没有意义的)一个值到之前定义的原子中。
      猜你喜欢
      • 2012-10-27
      • 1970-01-01
      • 1970-01-01
      • 2011-12-26
      • 1970-01-01
      • 1970-01-01
      • 2019-11-11
      • 1970-01-01
      • 2020-04-06
      相关资源
      最近更新 更多