views:

103

answers:

3

This might be a silly question, but:

Suppose an expression depends only on literals, or on other expressions that also only depend on literals; will the compiler evaluate this at compile time?

Suppose I have,

(def a (some-time-consuming-function some-literal))

(def b (some-other-time-consuming-function a))

Will both b and a be evaluated completely at compile time, so that the User isn't affected?

EDIT: Thanks very much, all of the answers were very helpful.

+5  A: 
Alex Taggart
I thought that macros might be a way around this. Three questions: 1. How did you go about testing this? (I thought about using the Ackerman function, and seeing at what point in the process I noticed a slowdown, but I don't know what the most rigorous way of doing this is.) 2. When is b evaluated -- at load time? 3. Don't the the sytax quote and unquote symbols cancel each other out? Anyway, thanks very much for the answer!
Rob Lachlan
1) I tested it by using a different metric, namely a function that called println. The message appears during compile but doesn't appear when loading the compiled namespace, thus one can conclude that the function was not being run at runtime. 2) The value being assigned to the var b is evaluated during macroexpansion. 3) Yep. It was mistakenly left over from an earlier iteration.
Alex Taggart
To be clear though, the answer to your question is "no." It's just that there are ways to achieve such behavior when you want to.
Alex Taggart
+3  A: 

In your example, the time-consuming functions are called only once, when your code is loaded.

The Clojure compiler does not try to optimize constant expressions, but the Java JIT compiler may do so in some cases.

Stuart Sierra
+3  A: 

Firstly, there is a pretty important reason why the right-hand-sides of defs need to be evaluated at load time: they might depend on the environment somehow and in the general case it's impossible to tell whether they do or not. Take e.g.

(def *available-processors* (.availableProcessors (Runtime/getRuntime)))

Secondly, here's one approach to testing what actually happens:

  1. Create a test project with Leiningen -- say, lein new testdefs.

  2. Put :main testdefs.core in project.clj.

  3. Put the following in src/testdefs/core.clj:

    (ns testdefs.core
      (:gen-class))
    
    
    (defn take-your-time [t]
      (printf "Taking my time (%d)...\n" t)
      (Thread/sleep t))
    
    
    (def a (take-your-time 5000))
    
    
    (defmacro frozen-def [v e]
      (let [val (eval e)]
        `(def ~v ~val)))
    
    
    (frozen-def b (take-your-time 5000))
    
    
    (defn -main [& args]
      (println "Starting...")
      (println a)
      (println b))
    
  4. Run lein uberjar; surely enough, the code takes its time twice.

  5. Run java -jar testdefs-1.0.0-SNAPSHOT-standalone.jar; you'll notice the code only takes its time once.

Michał Marczyk
Sorry for the weird spacing in the code -- looks like some SO Markdown weirdness or not-so-great Markdown-fu on my part (the second possibility seems more likely).
Michał Marczyk
I can't believe that I forgot that there's a sleep function.
Rob Lachlan