2010-10-22 4 views
8

У меня есть программа Clojure, которую я создаю как файл JAR с использованием Maven. Встроенный в JAR Manifest - это номер версии сборки, включая метку времени сборки.Установка констант Clojure во время выполнения

можно легко прочитать во время выполнения из JAR манифеста, используя следующий код:

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

, но мне сказали, что это плохая карма использовать def внутри defn.

Что такое Clojure-идиоматический способ установки константы во время выполнения? Я, очевидно, не имею информацию о версии сборки для встраивания в мой код как def, но я хотел бы, чтобы он устанавливал один раз (и для всех) из функции main при запуске программы. Затем он должен быть доступен как def для остальной части кода.

ОБНОВЛЕНИЕ: BTW, Clojure должен быть одним из самых крутых языков, с которыми я столкнулся довольно долго. Престижность Рику Хики!

ответ

7

Я по-прежнему считаю, что самый чистый способ - использовать alter-var-root в методе main вашего приложения.

(declare version) 

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

Он объявляет Var во время компиляции, устанавливает свое корневое значение во время выполнения один раз, не требует deref и не связан с основным потоком. Вы не ответили на это предложение в своем предыдущем вопросе. Вы пробовали этот подход?

+0

Я не пробовал, но буду. Это выглядит интересно. Отсутствие штрафа за производительность после установки значения. Я также могу использовать этот метод для установки значений из параметров командной строки - нужно установить только один раз. – Ralph

1

Надеюсь, я не пропущу что-нибудь на этот раз.

Если версия является постоянной, она будет определена один раз и не будет изменена. Вы можете просто удалить defn и сохранить (def version ...) в одиночку. Полагаю, вы не хотите этого по какой-то причине.

Если вы хотите изменить глобальные переменные в Fn я думаю, что более идиоматический способ заключается в использовании некоторого параллелизм конструкций для хранения данных и доступа и изменить его в безопасном режиме Например:

(def *version* (atom "")) 

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

@jneira: «Надеюсь, я на этот раз не пропущу». :-) Я не могу этого сделать, потому что тогда он оценивается во время компиляции (я считаю), и нет JAR-файла для чтения. – Ralph

+0

hehe :-P ok и второй вариант? (третий будет функция возвращает строки вместо выполнения redef, но может быть неэффективна для различных вызовов) – jneira

+0

Я думал, что мне нужно что-то сделать с атомами, я просто подумал, что это будет излишним, с накладными расходами синхронизации и т. д. Возможно, просто вызов функции 'def' из функции' main' будет в порядке, даже если он «нахмурился». – Ralph

4

Вы можете использовать динамическое связывание.

(declare *version*) 

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

Теперь main и каждая функция вызывается будет видеть значение *version*.

+0

+1, чтобы напомнить мне «и каждая функция, которую он называет», потому что это динамическое связывание, возможно, подходит для проблемы. – jneira

+0

Мне это нравится! Я собирался использовать атомы, но мне нужно будет изучить это дальше. Переменные (константы), которые мне нужно установить, должны быть установлены до того, как остальная часть программы будет работать в любом случае. Помимо номера версии, я также собираюсь использовать решение для записи параметров командной строки, полученных с помощью Apache commons-cli. – Ralph

+3

Помните об ограничении потока 'binding'. См. Также 'bound-fn'. – kotarak

2

Хотя решение kotarak работает очень хорошо, вот альтернативный подход: превратите свой код в memoized функцию, которая возвращает версию. Нравится так:

(def get-version 
(memoize 
    (fn [] 
    (-> (str "jar:" (-> my.ns.name (.getProtectionDomain) 
      (.getCodeSource) 
      (.getLocation)) 
     "!/META-INF/MANIFEST.MF") 
    (URL.) 
    (.openStream) 
    (Manifest.) 
    (.. getMainAttributes) 
    (.getValue "Build-number"))))) 
Смежные вопросы