views:

70

answers:

1

The language Forth offers a "compile-time" escape mechanism where code can be executed immediately, while the compiler is running (not at run-time). You can include print statements, for example, to debug tricky syntax or type errors).

Does Clojure have anything similar? I am getting a compile-time IllegalArgumentException in one of my function calls and would like to add a compile-time print statement to determine the argument type ((.getClass)).

Thanks.

UPDATE: Here is the complete defn that is failing compilation:

(ns my.ns.name
  (:gen-class
   :main true)
  (:use
   [clojure.contrib.str-utils2 :only (join)])
  (:import
   [java.io PrintWriter]
   [java.net URL]
   [java.util.concurrent Executors]
   [java.util.jar Manifest]
   [org.apache.commons.cli CommandLine HelpFormatter Options Option ParseException PosixParser]))

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

This defn works:

(defn set-version
  "Set the version variable to the build number."
  []
  (println (str (.getClass my.ns.name)))
  (def version
    (-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
                                   (.getCodeSource)
                                   (.getLocation))
                    "!/META-INF/MANIFEST.MF")
      (URL.)
      (.openStream)
      (Manifest.)
      (.. getMainAttributes)
      (.getValue "Build-number"))))
+2  A: 

Printing the class of things during compilation is pretty restricted to special cases. You will mostly get Symbols and Seqs. Only literals have a meaningful type during compilation. You can execute arbitrary code during compilation via macros.

(defmacro debug-type
  [x]
  (println (type x))
  x)

However as I said: this will normally not be very helpful. And no: in general you cannot wrap x in an eval, eg. if x is a symbol refering to a let-local.

EDIT: Update for updated question.

(def version
  (-> (str "jar:" (-> *ns* (.getProtectionDomain)
                           (.getCodeSource)
                           (.getLocation))
                  "!/META-INF/MANIFEST.MF")
    (URL.)
    (.openStream)
    (Manifest.)
    (.getMainAttributes)
    (.getValue "Build-number")))

Try this. There is no need for a function. def inside defn should ring alarm bells.

kotarak
@kotarak: I am trying to execute the following code: `(.. my.ns.name (getProtectionDomain) (getCodeSource) (getLocation))` and I get the IllegalArgumentException during AOT on my.ns.name ("no getProtectionDomain method"). If I change it to `(-> my.ns.name (.getProtectionDomain) (.getCodeSource) (.getLocation))` it works. Go figure.
Ralph
@Ralph: Hmm.. no clue. Did you try `*ns*` instead of the actual name? Did you clean any .class files between the two tries with `..` and `->`?
kotarak
@kotarak: Changing to `*ns*` moved the error to runtime :-). I cleaned the .class files before running Maven and got the same compile-time error.
Ralph
I added an update to my original question showing the actual code that fails to compile.
Ralph
@Ralph: I cannot refer to namespaces by symbols. So neither of your two initial forms should work, I guess. How can the problem move to runtime if you execute the code at compile time? Do you mean "for both forms" with "got the same c-t error" or "as before with `..`, but not `->`"?
kotarak
@kotarak: See my updated, updated original question.
Ralph
@ralph: See also updated answer.
kotarak
@kotarak: The "naked" def is nice, but does not work. The class is not running inside a JAR while it is being compiled, so I get a `Exception in thread "main" java.util.zip.ZipException: error in opening zip file`.
Ralph
You can try `(def version (when-not *compile-files* ...))` But then you will also run into trouble when loading the file directly during development. Maybe you need a guard like `(when-not (System/getProperty "running.in.dev.env") ...)` and set the property when developing and compiling.
kotarak
I guess I could do `(def version (if *compile-files* ... "not set")` using the :else part.
Ralph
That does not work either. The `def` is happening while the code is compiling. It needs to run when the program actually runs. Other than creating an atom, is there any way to set a variable once at runtime? Maybe I can embed the `def` code in `main`.
Ralph
Hmm.. Maybe you can do a `(alter-var-root #'version (constantly (-> 'my.name.space the-ns .getProtectionDomain ....)))` in `main`?
kotarak
I think I'll post another question on this specific issue -- setting "constants" once at runtime.
Ralph