Leonid Shevtsov
Leonid Shevtsov

Reputation: 14189

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

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?

Upvotes: 13

Views: 2161

Answers (4)

Alfred Xiao
Alfred Xiao

Reputation: 1788

What not just use defn instead of def.

(defn settings [] (load-settings "settings.json"))

Then use (settings) instead of settings.

Upvotes: 1

Leonid Shevtsov
Leonid Shevtsov

Reputation: 14189

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.

Upvotes: 13

Nicolas Modrzyk
Nicolas Modrzyk

Reputation: 14197

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)

Upvotes: 1

Leonid Beschastny
Leonid Beschastny

Reputation: 51490

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)

Upvotes: 10

Related Questions