Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure - how to make def forms evaluate at run time instead of compile time

Tags:

clojure

The core problem I'm trying to solve is: have a settings object that is loaded from a settings.json file, every time when the program is started.

I initially used code like

(def settings (load-settings "settings.json"))

but during deploy I was surprised to discover that the init form is being evaluated at compile time rather than run time - the compilation was failing because there was no settings.json file in place.

So the Y part of the problem is - can I delay the evaluation of the init form without employing refs or otherwise complicating the usage of the object? Or am I missing some core concept here?

like image 375
Leonid Shevtsov Avatar asked Jul 09 '15 17:07

Leonid Shevtsov


3 Answers

Answering my own question because I happened to find the exact thing I wanted thanks to @Alex.

So, quoting this thread from the Clojure mailing list from 2008,

While compiling, a compile-files flag is set. [...] If your file has some def initializers you don't want to run at compile-time, you can conditionalize them like this:

(def foo (when-not *compile-files* (my-runtime-init)))

in which case foo will be nil at compile time.

I tested the behavior with the following code:

(def evaluated-at-runtime
  (when-not *compile-files*
    (println "i am evaluated")
    "value"))

(defn -main [& args]
  (println evaluated-at-runtime))


>lein uberjar
... "(i am evaluated)" is not printed)

>java -jar test-app.jar
i am evaluated
value

There's a caveat here that the init form will be evaluated as soon as the program is launched, before the -main method, this may not be flexible enough for everyone, but then I direct you to one of the other great responses to this question.

like image 83
Leonid Shevtsov Avatar answered Nov 04 '22 18:11

Leonid Shevtsov


One possible solution is to load your settings file lazily, i.e. the first time it's actually used. You can do it with delay macro:

(def settings
  (delay (load-settings "settings.json")))

The only difference is that you'll have to deref your settings object every time you'll want to use it:

(println @settings)
like image 9
Leonid Beschastny Avatar answered Nov 04 '22 18:11

Leonid Beschastny


future and delay could both be used.

future runs in the background and avoid waiting for the evaluation to be finished.

delay would not do anything you try to access the value.

(def string-sample "(do (Thread/sleep 5000)  (println \"done\") 1)")

; execute immediately
(def settings (load-string string-sample))
(println settings)

; execute in the background, returns immediately
(def settings (future (load-string string-sample )))
(println @settings)

; evaluated when calling it
(def settings (delay (load-string string-sample)))
(println @settings)
like image 1
Nicolas Modrzyk Avatar answered Nov 04 '22 18:11

Nicolas Modrzyk