In general there is no way to see body of a closure at runtime.
The "body" is part of the source code. It gets compiled into bytecode (and later to machine code by the JIT). The closure consists at runtime of the captured values for the free variables in the body and a pointer to the code.
That said, you might be satisfied with studying the output of expand
or compile
.
Now expand
only expands syntax, so you won't be able to see any optimizations yet:
> (expand-syntax #'(define a
((λ (x) x)
((λ (y) y)
(λ (z)
((λ (w) w) z))))))
(define-values (a)
(#%app (lambda (x) x)
(#%app (lambda (y) y)
(lambda (z)
(#%app (lambda (w) w) z)))))
Note that #%app
means "application".
To see the result after optimizations are applied we need to invoke the compiler.
The builtin compile
produces bytecodes, so we use compile-zo
which converts the bytecode into structures representing bytecode (and they print nicely in the repl).
> (compile-zo '((λ (x) x)
((λ (y) y)
(λ (z)
((λ (w) w) z)))))
'#s((compilation-top zo 0)
1
#s((prefix zo 0) 0 () ())
#s((application expr 0 form 0 zo 0)
#s((closure expr 0 form 0 zo 0)
#s((lam expr 0 form 0 zo 0)
()
(preserves-marks single-result)
1
(val)
#f
#()
()
#f
6
#s((localref expr 0 form 0 zo 0) #f 0 #f #f #f))
closure64862)
(#s((closure expr 0 form 0 zo 0)
#s((lam expr 0 form 0 zo 0)
y
(preserves-marks single-result)
1
(val)
#f
#()
()
#f
6
#s((localref expr 0 form 0 zo 0) #f 0 #f #f #f))
y64863))))
There is only one application left, so the program was indeed simplified during compilation.
See https://github.com/soegaard/meta/blob/master/runtime/racket-eval.rkt for a definition of compile-zo
.
Finally yet another option is to look at the machine code produced by the JIT.
See https://github.com/samth/disassemble
UPDATE
If I read the bytecode correctly, it corresponds to:
((λ (x) x)
(λ (y) y))