On 14-Apr-08, at 3:58 PM, Derek Peschel wrote:
On Mon, Apr 14, 2008 at 02:03:37PM -0400, Joel J. Adamson wrote:
Is there somewhere I can read more about this?
The manual doesn't mention type safety or the namespace feature (which the ## prefix is one instance of) but the Wiki has a page on them. ## means "internal" more than "unsafe". Many of the internal procedures are unsafe, though, and all unsafe procedures are supposed to be internal.
http://dynamo.iro.umontreal.ca/~gambit/wiki/index.php/Namespaces
Comments from new readers might improve the page. That's why I'm posting to the list.
Here's a short explanation of the ## prefix.
Gambit is a "Scheme in Scheme", meaning that most of the code (including the Gambit compiler, the Gambit interpreter and the Gambit runtime system and libraries) is written in Scheme and compiled with the Gambit compiler. OK, so how does this work? Take something simple like the car procedure. How can car be implemented in Scheme? It obviously can't be implemented as:
(define (car x) (car x))
Since this is an infinite recursion. Gambit's implementation of the car procedure corresponds roughly to this:
(define (car x)
(declare (extended-bindings))
(if (pair? x) (##car x) (error "car expects a pair!")))
The ##car procedure which is called by car is a version of car which assumes that the parameter is a pair. In other words ##car is simply an indirection (with offset). Thanks to the extended-bindings declaration the compiler knows that the reference to the ##car variable truly refers to the ##car procedure, so the compiler can inline this operation. The resulting C code for the body of car is roughly the following:
/* x is a ___SCMOBJ, which is of type int */
if (___PAIRP(x)) result = ___CAR(x); else error(...);
where ___PAIRP and ___CAR are defined in include/gambit.h as something like:
#define ___tPAIR 3 #define ___PAIRP(x) (((x) & 3) == ___tPAIR) #define ___CAR(x) (___SCMOBJ*)((x) - ___tPAIR)[1] #define ___CDR(x) (___SCMOBJ*)((x) - ___tPAIR)[2]
The original purpose of the ## prefix is to give a non-conflicting name for the primitive operations, such as ##cons, ##car, ##pair?, which are needed to implement more complex procedures such as append and write. Note that the ## prefix is illegal in Scheme, so these names (which are accepted by the Gambit reader) will never conflict with variable names defined by portable Scheme code. This is fairly common practice, for instance MzScheme uses the #% prefix for these "hidden" names.
The ## prefix has also been used to "keep out of the user's way" some of the runtime's utility procedures which are not meant to be called directly by the user. For performance reasons, these procedures normally do not check the type of their parameters, and they only call other ## procedures. As a general rule for most non-## procedures provided by the runtime system there is a corresponding ## procedure which does the same operation without type checks, and sometimes with slightly different parameters. For example the ##append procedure takes exactly two parameters and it does not require that the first parameter is a proper list (any non-pair object is treated as the empty list), so (##append 11 '(22 . 33)) => (22 . 33). This makes it easy to enforce one of Scheme's requirements that the redefinition of a predefined global variable does not change the behaviour of procedures which would seem to have to call them. For example the naive (and incorrect) definition of list-ref is:
(define (list-ref lst i) (if (= i 0) (car lst) (list-ref lst (- i 1))))
It is incorrect because doing (set! = <) or (set! car cdr) would change the behaviour of list-ref. Instead, list-ref is implemented roughly as
(define (list-ref lst i) (##list-ref lst i))
(define (##list-ref lst i)
(declare (extended-bindings))
(if (##= i 0) (##car lst) (##list-ref lst (##- i 1))))
That way a mutation of any of the global variables =, car, -, list-ref will not change the behaviour of the list-ref procedure:
(let ((lr list-ref)) (set! = <) (set! list-ref vector-ref) (lr '(11 22 33) 1)) => 22
So as a general rule:
1) ## procedures are unsafe (they assume the parameters are of the appropriate type) 2) simple ## procedures are inlinable by the compiler (##car, ##vector- ref, ##fixnum.+, etc) 3) if the runtime system provides procedure P, then ##P does the same operation without checking the parameters
Marc