2010-10-22 11 views
8

Tengo un programa Clojure que construyo como un archivo JAR utilizando Maven. Embebido en el Manifiesto JAR es un número de versión de compilación, que incluye la marca de tiempo de compilación.Configuración de Clojure "constantes" en el tiempo de ejecución

puedo leer fácilmente esto en tiempo de ejecución de la jarra manifiesto mediante el siguiente código:

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

pero me han dicho que es un mal karma a utilizar def dentro defn.

¿Cuál es la forma idiomática de Clojure para establecer una constante en el tiempo de ejecución? Obviamente no tengo la información de la versión de compilación para incrustar en mi código como def, pero me gustaría configurarlo una vez (y para todos) desde la función main cuando se inicia el programa. Debería estar disponible como def en el resto del código de ejecución.

ACTUALIZACIÓN: Por cierto, Clojure tiene que ser uno de los mejores idiomas que he encontrado en mucho tiempo. ¡Felicitaciones a Rich Hickey!

Respuesta

7

Sigo pensando que la manera más limpia es usar alter-var-root en el método main de su aplicación.

(declare version) 

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

Declara el Var en tiempo de compilación, establece su valor de la raíz en tiempo de ejecución una vez, no requiere DEREF y no está vinculada al hilo principal. No respondiste a esta sugerencia en tu pregunta anterior. ¿Has probado este enfoque?

+0

No lo he probado, pero lo haré. Se ve interesante. Sin penalización de rendimiento una vez que se establece el valor. También puedo usar esta técnica para establecer valores desde las opciones de línea de comandos; necesito establecer solo una vez. – Ralph

1

Espero no perderme algo esta vez.

Si la versión es una constante, se va a definir una vez y no se va a cambiar, se puede eliminar simplemente la definición y mantener la (versión def ...) sola. Supongo que no quieres esto por alguna razón.

Si desea cambiar las variables globales en un fn Creo que la forma más idiomática es el uso de algunas de las construcciones de concurrencia para almacenar los datos y el acceso y cambiar de una manera segura Por ejemplo:

(def *version* (atom "")) 

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

@jneira: "Espero no perderme algo esta vez". :-) No puedo hacer eso porque luego se evalúa en tiempo de compilación (creo) y todavía no hay ningún archivo JAR para leer. – Ralph

+0

jeje :-P ok y la segunda opción? (la tercera sería la función devuelve las cadenas en lugar de hacer una redef, pero podría ser ineficiente para varias llamadas) – jneira

+0

Estaba pensando que tendría que hacer algo con los átomos, pensé que sería excesivo, con la sobrecarga de sincronización, etc. . Tal vez simplemente llamar a la 'def' desde la función 'main' estaría bien, incluso si está" mal visto ". – Ralph

4

Puede usar el enlace dinámico.

(declare *version*) 

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

Ahora main y cada función que llama verá el valor de *version*.

+0

+1 para recordarme que "y cada función que llama" causa que sea un enlace dinámico, tal vez se ajusta al problema – jneira

+0

¡Me gusta esto! Iba a usar átomos, pero tendré que explorar más a fondo. Las variables (constantes) que necesito establecer deben configurarse antes de que el resto del programa se ejecute de todos modos. Además del número de versión, también voy a usar la solución para registrar las opciones de línea de comando obtenidas con Apache commons-cli. – Ralph

+3

Recuerde el límite de hilos de 'binding '. Ver también 'bound-fn'. – kotarak

2

Si bien la solución de Kotarak funciona muy bien, este es un enfoque alternativo: convierta su código en una función memorada que devuelve la versión. De esta manera:

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