Ralph
Ralph

Reputation: 32284

Setting Clojure "constants" at runtime

I have a Clojure program that I build as a JAR file using Maven. Embedded in the JAR Manifest is a build-version number, including the build timestamp.

I can easily read this at runtime from the JAR Manifest using the following code:

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

but I've been told that it is bad karma to use def inside defn.

What is the Clojure-idiomatic way to set a constant at runtime? I obviously do not have the build-version information to embed in my code as a def, but I would like it set once (and for all) from the main function when the program starts. It should then be available as a def to the rest of the running code.

UPDATE: BTW, Clojure has to be one of the coolest languages I have come across in quite a while. Kudos to Rich Hickey!

Upvotes: 9

Views: 1401

Answers (4)

Siddhartha Reddy
Siddhartha Reddy

Reputation: 6211

While kotarak's solution works very well, here is an alternative approach: turn your code into a memoized function that returns the version. Like so:

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

Upvotes: 2

kotarak
kotarak

Reputation: 17299

I still think the cleanest way is to use alter-var-root in the main method of your application.

(declare version)

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

It declares the Var at compile time, sets its root value at runtime once, doesn't require deref and is not bound to the main thread. You didn't respond to this suggestion in your previous question. Did you try this approach?

Upvotes: 7

Brian Carper
Brian Carper

Reputation: 72926

You could use dynamic binding.

(declare *version*)

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

Now main and every function it calls will see the value of *version*.

Upvotes: 4

jneira
jneira

Reputation: 944

I hope i dont miss something this time.

If version is a constant, it's going to be defined one time and is not going to be changed you can simple remove the defn and keep the (def version ... ) alone. I suppose you dont want this for some reason.

If you want to change global variables in a fn i think the more idiomatic way is to use some of concurrency constructions to store the data and access and change it in a secure way For example:

(def *version* (atom ""))

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

Upvotes: 1

Related Questions