El 11 de junio de 2010 15:25, Marc Feeley feeley@iro.umontreal.caescribió:
On 2010-06-11, at 8:34 AM, Álvaro Castro-Castilla wrote:
Hi,
I can see sometimes these kind of procedures ##car, ##cdr, ##null? etc...
in code that has been optimized for Gambit. What is exactly the difference between those and the standard ones? And if these ones are faster, why are they defined as separate functions instead of just substituting the standard ones when in (unsafe) compilation? Then, how are they really used?
Gambit has a namespace mechanism and identifiers can be qualified with the
namespace. So foo#bar is the identifier bar in the namespace foo. The namespace declaration allows mapping unqualified identifiers to a particular namespace, so with the code
(namespace ("foo#" bar baz))
(bar 123) (other#bar 999)
the call to bar is a call to foo#bar and the call to other#bar is really a call to other#bar. So regardless of the namespace declarations, a call to ##car will really call ##car.
The Gambit source uses namespace declarations sparingly. From memory there are 3 namespaces in the runtime system:
- "c#" is for the Gambit compiler
- "sc#" is for the syntax-case expander
- "##" is for the Gambit runtime system procedures
The Gambit runtime system procedures implement a wide variety of functionality, from simple "primitive" procedures (such as ##car, ##pair?, ...) that translate to a few machine instructions, to complex procedures such as ##eval (the interpreter), ##repl (the REPL), etc.
Gambit's runtime system is built on top of the primitive procedures. Given that most of the runtime system is written in Scheme, for performance it is important to have primitives which assume that there are no run time errors. So ##car assumes that its argument is a pair. The car procedure on the other hand does type checking at run time. In fact, car is defined like this (more or less):
(define (car x) (if (##pair? x) (##car x) (error "pair expected"))
When the "not safe" declaration is used (and "standard-bindings"), the compiler can avoid the type checking. So what the compiler does is replace the call to car by a call to ##car. Here's an example:
% cat sum.scm (declare (standard-bindings) (not safe))
(define (sum x) (fx+ (car x) (cdr x)))
% gsc -expansion sum.scm Expansion:
(define sum (lambda (x) ('#<procedure #2 ##fx+> ('#<procedure #3 ##car> x) ('#<procedure #4 ##cdr> x))))
As you see, it is now explicit what is being called.
Thanks for the deep explanation, it was really useful.
Now why are qualified names (such as ##car) used all over the place in the runtime system? Mostly for historical reasons (a large part of the runtime system was written before the existence of the namespace mechanism and the procedure specializer (i.e. car + "not safe" -> ##car). I've been wanting to "clean things up" for a while now, but that's a major undertaking that adds little tangible value to Gambit users. Perhaps someone on this list who likes manual labor would like to refactor the Gambit runtime system... You know where to reach me if you have the urge!
Marc
Actually I would expect to see that procedures in the runtime implementation, and I think that is even good, and also saves (perhaps?) a tiny amount of computation avoiding all the transformations. I wondered about code that I was reading from different places, of libraries made for Gambit that I use as reference.
Thanks again for the explanations. Best regards,
Álvaro