views:

126

answers:

4

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!

+1  A: 

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* ...))
jneira
@jneira: "I hope i dont miss something this time." :-) I can't do that because then it evaluates at compile-time (I believe) and there is no JAR file to read yet.
Ralph
hehe :-P ok and the second option? (the third would be the function returns the strings instead doing a redef but it could be inefficient for various calls)
jneira
I was thinking I would need to do something with atoms, I just thought that would be overkill, with the synchronization overhead, etc. Maybe just calling the `def` from the `main` function would be OK, even if it is "frowned upon".
Ralph
On second thought, the overhead for **reading** an atom should be very low, and it will only be set once, so maybe using an atom is the best approach.
Ralph
(def version) would be compile time. The alternative to calling def within a function is to swap! (or reset!, because the initial value is meaningless) a value into previously defined atom.
Alex Stoddard
I'll use an atom. To be honest, I won't be reading the version number that often :-).
Ralph
+4  A: 

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*.

Brian Carper
+1 to remind me the "and every function it calls" cause being a dynamic binding, maybe fits the problem
jneira
@Brian: I like this! I was going to use atoms, but I'll need to explore this further. The variables (constants) I need to set should all be set before the rest of the program runs anyway. Besides the version number, I am also going to use the solution for recording command line options obtained with Apache commons-cli.
Ralph
Remember the thread-boundedness of `binding`. See also `bound-fn`.
kotarak
Current problem is that bindings only apply to the thread that creates them. So if a thread pool is used somewhere down the call stack (e.g. with pmap) then the bindings are lost - from Rich Hickey's talk at ClojureConj
Ralph
+4  A: 
kotarak
@kotarak: I haven't tried it, but I will. It looks interesting. No performance penalty once the value is set. I can also use this technique for setting values from command line options -- need to set only once.
Ralph