Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will the clojure compiler automatically evaluate expressions of literals at compile time?

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.

EDIT 6.6.2011: Turns out that if you try and use this technique to precompute a very large data structure, it's easy to make class files that are too big to load. In those cases you want to create a file which will be read in rather than a class file that will be loaded. The macro tricks described in these answers should only be applied as is in situations where the return value is not a prohibitively large structure.

The error thrown is: "java.lang.ClassFormatError: Invalid this class index" See this thread for discussion of a related situation.

like image 472
Rob Lachlan Avatar asked Aug 18 '10 22:08

Rob Lachlan


3 Answers

Not silly at all, I had to think about it and test it out.

It will work only if you use macros instead of functions, since the body of a macro is evaluated at compile/macroexpansion-time. E.g.:

(defmacro preprocess [f & args]
  (let [x# (apply (resolve f) args)]
    `~x#))

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

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

Then a and b are def'd to the values returned from evaluating preprocess.

like image 161
Alex Taggart Avatar answered Oct 24 '22 08:10

Alex Taggart


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.

like image 4
Michał Marczyk Avatar answered Oct 24 '22 10:10

Michał Marczyk


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.

like image 3
Stuart Sierra Avatar answered Oct 24 '22 10:10

Stuart Sierra