JavaScript? programmers are familiar with accessing the source code of a procedure at runtime. Can we do this in Scheme?
We would also like to store other meta-information about a procedure, say, to provide a documentation string for the procedure, similar to Emacs Lisp doc-strings.
The answer is a qualified "yes,"
if you really want to, provided that the procedure is created with a special form described here. But see the caveat below for a discussion of the dangers of exposing procedure source code.
(module doc-lambda mzscheme
(define-values (struct:doc-lambda make-doc-lambda doc-lambda? doc-lambda-ref doc-lambda-set!)
(make-struct-type 'doc-lambda #f 3 0 #f null #f 0))
(define doc-lambda-proc (make-struct-field-accessor doc-lambda-ref 0 'proc))
(define doc-lambda-doc (make-struct-field-accessor doc-lambda-ref 1 'doc))
(define doc-lambda-src (make-struct-field-accessor doc-lambda-ref 2 'src))
(define (procedure-doc x)
(and (doc-lambda? x)
(doc-lambda-doc x)))
(define (procedure-source x)
(and (doc-lambda? x)
(doc-lambda-src x)))
(define-syntax doc-lambda
(syntax-rules ()
[(_ doc-string args body0 body1 ...)
(let ([doc-string-v doc-string])
(make-doc-lambda
(lambda args body0 body1 ...)
doc-string-v
`(doc-lambda ,doc-string-v args body0 body1 ...)))]))
(provide doc-lambda procedure-doc procedure-source))
The
doc-lambda macro turns its source code into an S-expression by quoting it and storing it in a struct. It creates the actual procedure and stores that in the struct as well, along with the provided documentation string.
The struct is executable as a procedure thanks to
MzScheme's "structs as procedures" (see chapter 4.6 in the
MzScheme language manual): the final argument to
make-struct-type tells
MzScheme to treat the 0th item in the struct as the procedure to invoke when the struct is applied as a procedure.
Exposing a function's source code might be useful for debugging, for example, where a quick view of the code can help you discover the source of a bug. But generally, this can lead to dependencies on the internal implementation of a procedure. If external code relies on the procedure's source code, changing the implementation of the procedure breaks the external code.
If all you want is a way to represent a procedure as data that can be stored and reinvoked later, simply store and invoke the closure directly. Don't use this recipe simply to call
eval on procedure source. (In addition to the many evils of
eval, that approach will likely break if the procedure source refers to variables in scope other than just the procedure arguments.)
For newcomers it might be worth noting this recipe is about "it can be done if you
really want to" rather than "this approach is recommended".
--
JensAxelSoegaard - 16 May 2006
Good point. I've added the caveat section.
--
DaveHerman - 16 May 2006
--
DaveHerman - 16 May 2006