Hello
apply is quite a handy simple mechanism for destructuring lists, but: if the list doesn't match the function prototype, an exception is thrown. Sometimes I don't want to get an exception but instead try to apply another function (more generally: to back-track).
So I'm looking for an alternative apply function which takes an alternative continuation as a third argument (instead of the implicit consing mechanism offered by standard apply when giving it multiple arguments):
(cond-apply fn1 lis (lambda () (cond-apply fn2 lis (lambda () (error "no match:" lis)))))
or
(or (cond-apply fn1 lis) (cond-apply fn2 lis) (error "no match:" lis))
call-with-exception-* don't do any good since it's relatively slow and, worse, would leave the handler in place while the called function is running.
I'm remembering a lowlevel function to get the arity of a function (returning a positive fixnum for a fixed number of arguments, and a negative fixnum if the function is taking a rest argument). Strangely, I cannot find it again now. What is it called? (Or did I see it in Chicken, not Gambit?) If that function is efficient, I could write cond-apply in a fairly efficient way.
Thanks Christian.
Afficher les réponses par date
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 12-Nov-06, at 9:07 AM, Christian wrote:
Hello
apply is quite a handy simple mechanism for destructuring lists, but: if the list doesn't match the function prototype, an exception is thrown. Sometimes I don't want to get an exception but instead try to apply another function (more generally: to back-track).
So I'm looking for an alternative apply function which takes an alternative continuation as a third argument (instead of the implicit consing mechanism offered by standard apply when giving it multiple arguments):
(cond-apply fn1 lis (lambda () (cond-apply fn2 lis (lambda () (error "no match:" lis)))))
or
(or (cond-apply fn1 lis) (cond-apply fn2 lis) (error "no match:" lis))
call-with-exception-* don't do any good since it's relatively slow and, worse, would leave the handler in place while the called function is running.
I'm remembering a lowlevel function to get the arity of a function (returning a positive fixnum for a fixed number of arguments, and a negative fixnum if the function is taking a rest argument). Strangely, I cannot find it again now. What is it called? (Or did I see it in Chicken, not Gambit?) If that function is efficient, I could write cond-apply in a fairly efficient way.
The concept of "the arity of a procedure" is ill-defined in general when you have optional parameters. The mechanism you mention exists at compile-time for macros, but that is of no use at run time.
The simplest thing to do is to use call/cc and exceptions, but as you mention that is moderately expensive and has scoping problems.
What you really want is for cond-apply to save the error-continuation in the continuation (on the stack), which is really inexpensive, and to access the error-continuation only when there is a wrong-number-of- arguments exception. This can be done with this code, which should be compiled:
(declare (extended-bindings) (block) (not inline))
(define (cond-apply proc args $wrong-nb-args)
(declare (not interrupts-enabled) (environment-map))
(let ((results (##apply proc args))) (##first-argument $wrong-nb-args) results))
(define (##raise-wrong-number-of-arguments-exception-nary proc . args)
(declare (not interrupts-enabled))
(wrong-number-of-arguments proc args))
(define (##raise-wrong-number-of-arguments-exception proc args)
(declare (not interrupts-enabled))
(wrong-number-of-arguments proc args))
(define (wrong-number-of-arguments proc args)
(define (err) (error "wrong number of arguments" proc args))
(##continuation-capture (lambda (cont) (if (##eq? cond-apply (##subprocedure-parent (##continuation-ret cont))) (let ((binding (##continuation-locals cont '$wrong-nb-args))) (if (##pair? binding) (let (($wrong-nb-args (##cdr binding))) ($wrong-nb-args)) (err))) (err)))))
(pp (cond-apply cons '(1 2 3) (lambda () (cond-apply sin '(1 2 3) (lambda () 999)))))
All of this works because the procedures ##raise-wrong-number-of- arguments-exception and ##raise-wrong-number-of-arguments-exception- nary are tail-called by ##apply (i.e. continuation = inside the cond- apply) so we can simply check the continuation frame at the top of the stack to find the value of $wrong-nb-args. The (declare (environment-map)) is necessary to keep the name of the variables in the continuation frame so that ##continuation-locals can find it.
Marc
Hello Marc
Thank you for your code, it's really interesting for me. (Especially since I'm planning to write an error procedure which can skip continuation frames (like Perl's croak, which skips stack frames in the current module or parent classes).)
Some notes: - (cond-apply sin '(1 2) (lambda (x) 'foo)) will enter an endless loop because the call of the non-thunk procedure will re-enter |wrong-number-of-arguments|. - but it seems ##raise-wrong-number-of-arguments-exception is not being called by the apply call (?, I haven't checked the sources); commenting out this function definition gets rid of the above loop.
Some questions: - what does ##first-argument do? (- is the environment-map directive automatically activated by the |debug| compiler flag? Or will adding this directive improve variable display in the debugger?)
I'll experiment with ##raise-unknown-keyword-exception etc. to make it more complete.
Christian.