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?
Thanks a lot,
Álvaro
Afficher les réponses par date
Hallo,
2010/6/11 Álvaro Castro-Castilla alvaro.castro.castilla@gmail.com:
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?
$ gsi
(car '())
*** ERROR IN (console)@1.1 -- (Argument 1) PAIR expected (car '()) 1> ,d
(##car '())
Segmentation fault
Cheers,
El 11 de junio de 2010 14:51, Alex Queiroz asandroq@gmail.com escribió:
Hallo,
2010/6/11 Álvaro Castro-Castilla alvaro.castro.castilla@gmail.com:
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?
$ gsi
(car '())
*** ERROR IN (console)@1.1 -- (Argument 1) PAIR expected (car '()) 1> ,d
(##car '())
Segmentation fault
Cheers,
Hi, thanks for your answer. But that is what I meant by the unsafe compilation. Why isn't this accomplished just with standard procedures plus the (not safe) declaration? so the code is more portable and also looks nicer...
2010/6/11 Álvaro Castro-Castilla alvaro.castro.castilla@gmail.com
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?
(declare (safe)) (car x) ; Argument typechecking made (##car x) ; No argument typechecking made
(declare (not safe)) (##car x) ; No argument typechecking made (car x) ; No argument typechecking made
And if these ones are faster, why are they defined as separate functions instead of just substituting the standard ones when in (unsafe) compilation?
Substitution is made automatically on (not safe) compilation.
Then, how are they really used?
The purpose of ##:s may be to explicitly declare unsafe use of a procedure, as the programmer is 100% sure the passed type is always correct. A hypothethical example would be (define (cdr-nice pair) (and (pair? pair) (##cdr pair))) .
Thanks a lot,
Álvaro
Mikael
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/gambit-list
2010/6/11 Mikael mikael.trash@gmail.com
2010/6/11 Álvaro Castro-Castilla alvaro.castro.castilla@gmail.com
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?
(declare (safe)) (car x) ; Argument typechecking made (##car x) ; No argument typechecking made
(declare (not safe)) (##car x) ; No argument typechecking made (car x) ; No argument typechecking made
And if these ones are faster, why are they defined as separate functions instead of just substituting the standard ones when in (unsafe) compilation?
Substitution is made automatically on (not safe) compilation.
Ah! ok, so it is actually done, but these procedures allow for using those unsafe versions even in "safe" compilation...
Thanks a lot!
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.
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
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