Macro magic – typeof and primitive?

Posted: August 8, 2012 in Uncategorized

Recently I needed to determine the type of an expression at compile time. Clojure provides a useful function type, but this wasn’t suitable for my purposes as it only gives you the runtime type.

Luckily there is a function in the old clojure.contrib/repl-utils that does something close to what I wanted:

(defn expression-info
 "Uses the Clojure compiler to analyze the given s-expr. Returns
 a map with keys :class and :primitive? indicating what the 
 compiler concluded about the return value of the expression. 
 Returns nil if no type info can be determined at compile-time.

 Example: (expression-info '(+ (int 5) (float 10)))
 Returns: {:class float, :primitive? true}"
 (let [fn-ast (Compiler/analyze Compiler$C/EXPRESSION 
                `(fn [] ~expr))
       expr-ast (.body (first (.methods fn-ast)))]
   (when (.hasJavaClass expr-ast)
     {:class (.getJavaClass expr-ast)
      :primitive? (.isPrimitive (.getJavaClass expr-ast))})))

This does pretty much what is needed in terms of running and querying an expression via the internals of the Clojure compiler. So all that remained was to wrap it in a couple of macros:

(defmacro typeof 
    (:class (expression-info expression)))

(defmacro primitive? 
    (:primitive? (expression-info expression))))

Note that there is no need for quoting here, because we aren’t using the macros to generate code – we actually want the macros to run at compile time using the unevaluated expression form.

Here’s how they work:

(typeof 1)
=> long

(typeof [1 2 3])
=> clojure.lang.PersistentVector

(typeof (str "Hello" " " "World))
=> java.lang.String

(primitive? (+ 2 3))
=> true

(primitive? (+ 2N 3N))
=> false

(primitive? (let [a [1]] (+ 2 (first a))))
=> false

The last one is interesting: in theory a smart enough compiler could work out that (first a) must be a java.lang.Long and as a result use primitive arithmetic. Clojure doesn’t currently do this – but I guess it might be possible in the future if the Typed Clojure functionality ever makes it back into the compiler. This would be valuable from a performance perspective because currently Clojure gets slowed down in some numerical code from doing too much boxing, unboxing and type-checking of numbers.

  1. Operators called typeof usually get the dynamic type of a value; it might be less confusing to call it something like static-type.

    Macros that quote their arguments are handy for operations frequently used at the REPL, like doc, but these aren’t, so they should be functions.

  2. Java Sucks says:

    expression-info doesn’t compile. It produces the following Java error:

    CompilerException java.lang.RuntimeException: No such namespace: Compiler$C, compiling:(NO_SOURCE_PATH:24:15)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s