I made a little proof of concept last night. You could always look at bytecode that clojure emits in few ways, you can either hack the compiler yourself, or force AOT in your project and use javap. The first approach is a bit intrusive, and the second has a somewhat annoying turnaround time, and won't work for any code that calls eval at runtime.
This takes another approach. It uses java's Instrumentation stuff, to provide a hook into all classloading past the point at which it's loaded, where I store off all the bytes of defined classes into a big ConcurrentHashMap. Then I use eclipse.jdt.core's disassembler functionality to print out the bytecode.
There's a nice lein-plugin that adds the dependency to your project and looks itself up in order to add the appropriate -javaagent flag to your jvm startup options.
Now you can do something like this:
user=> (require 'no.disassemble)
nil
user=> (in-ns 'no.disassemble)
#<Namespace no.disassemble>
no.disassemble=> (println (disassemble (fn [])))
// Compiled from NO_SOURCE_FILE (version 1.5 : 49.0, super bit)
public final class no.disassemble$eval1174$fn__1175 extends clojure.lang.AFunction {
// Method descriptor #7 ()V
// Stack: 0, Locals: 0
public static {};
0 return
Line numbers:
[pc: 0, line: 1]
// Method descriptor #7 ()V
// Stack: 1, Locals: 1
public disassemble$eval1174$fn__1175();
0 aload_0
1 invokespecial clojure.lang.AFunction() [10]
4 return
Line numbers:
[pc: 0, line: 1]
// Method descriptor #12 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
public java.lang.Object invoke();
0 aconst_null
1 areturn
Line numbers:
[pc: 0, line: 1]
Local variable table:
[pc: 0, pc: 1] local: this index: 0 type: java.lang.Object
}
nil
no.disassemble=>
Potential use cases:
Writing fast code faster.
Proving to newbies that clojure is not interpreted, further evidence of eval's coolness.
Checking to make sure primitives are being used where you expect.