Define is core syntactic form, partially because it expands differently based on context, becoming a top-level
set! when the define is at the top-level of a script file, and part of a
letrec* when it is an internal definition (including within a scheme library or program).
It is worth noting though that just because Chez Scheme provides a base define, doesn't mean you cannot create your own define macro, after all there are no reserved words in Scheme.
You could build a R6RS library that exports a define supporting docstrings and a documentation function for looking up the values. You could do this as a couple of libraries with standard R6RS features based on the implementation for Chicken by Zane Ashby:
docstring-helper.sls which defines a hash table to store document strings in:
(library (docstring-helper)
(export docs-ht documentation)
(import (rnrs))
(define docs-ht (make-eq-hashtable))
(define documentation
(lambda (name)
(hashtable-ref docs-ht name #f))))
docstring.sls which uses the hash table and redefines define:
(library (docstring)
(export define documentation)
(import (rename (rnrs) (define rnrs:define)) (docstring-helper))
(define-syntax define
(lambda (x)
(syntax-case x ()
[(_ (name . fmls) str body0 ... body1)
(and (identifier? #'name) (string? (syntax->datum #'str)))
(begin
(hashtable-set! docs-ht (syntax->datum #'name) (syntax->datum #'str))
#'(rnrs:define (name . fmls) body0 ... body1))]
[(_ name str expr)
(and (identifier? #'name) (string? (syntax->datum #'str)))
(begin
(hashtable-set! docs-ht (syntax->datum #'name) (syntax->datum #'str))
#'(rnrs:define name expr))]
[(_ name . rest) #'(rnrs:define name . rest)]))))
This can then be used as follows:
(import (docstring))
(define foo "A constant that represents the answer to the question of the universe" 42)
foo ;=> 42
(documentation 'foo) ;=> "A constant that represents the answer to the question of the universe"
This may not work in some R6RS Scheme implementations, depending on how they handle stateful elements around library phasing, but it will work in Chez Scheme. You could also do something that attaches the document strings directly to the identifier being defined. This is potentially useful when different libraries name the same function. In the hash table version, the docstring would only be preserved for one, but if define-property is used instead of a hash table, the record on the identifiers is kept distinct:
docstring.chezscheme.sls (or you could just name it docstring.ss/docstring.sls if this is the only implementation you have around):
(library (docstring)
(export define documentation)
(import (rename (chezscheme) (define cs:define)))
(cs:define docstring #f) ;; identifier to hang property on
(define-syntax define
(lambda (x)
(syntax-case x ()
[(_ (name . fmls) str body0 ... body1)
(and (identifier? #'name) (string? (datum str)))
#'(begin
(cs:define (name . fmls) body0 ... body1)
(define-property name docstring str))]
[(_ name str expr)
(and (identifier? #'name) (string? (datum str)))
#'(begin
(cs:define name expr)
(define-property name docstring str))]
[(_ name . rest) #'(cs:define name . rest)])))
(define-syntax documentation
(lambda (x)
(lambda (r) ;; retrieve the syntactic environment
(syntax-case x ()
[(_ id) (identifier? #'id) (r #'id #'docstring)]
[(_ 'id) (identifier? #'id) (r #'id #'docstring)])))))
Then this can be used as follows:
(import (docstring))
(define foo "A constant that represents the answer to the question of the universe" 42)
foo ;=> 42
(documentation foo) ;=> "A constant that represents the answer to the question of the universe"
(documentation 'foo) ;=> "A constant that represents the answer to the question of the universe"
(let ([foo 5]) (write (documentation foo)) (newline) foo)
;=> #f
;=> 5
I've kept the quoted syntax here, even though it is not necessary. You can see from the later example that if the local identifier differs from the one defined above, it will shadow (but not alter) the original definition.
Hope that helps.
-andy:)