yesterday at lunch I was trying to describe my problems generalizing my generator macro to handle arbitrary lambda formals lists. Ryan was the one who summed the problem up best; I think he said "there is a asymmetry between the format of the formals list and the format of invocation"
The problem in essence is that there are two distinct cases that are easy.
If you want to match a proper list of formals and generate a corresponding invocation, you do something like:
(define-syntax adder
(syntax-rules ()
((_ (args ...) k ...)
(lambda (args ...) (+ k ... args ...)))))
And, if you want to do variable arity, you can do it by matching an identifier representing the whole list of arguments
(define-syntax adder
(syntax-rules ()
((_ argl k ...)
(lambda argl (apply + k ... argl)))))
But notice the difference between the bottom and the top. If some users of the
adder
macro want to be able to specify that they are generating a procedure of arity 2, with appropriate error detection, they can use the first but not the second. And of course, the first does not do variable arity procedures.To deal with this problem in the context of my generator macro, I introduced a helper macro that builds up an application expression based on the form of the formals list.
(define-syntax build-apply
(syntax-rules ()
((build-apply (rator rands ...) (args ...)) ;; usual case
(rator rands ... args ...))
((build-apply (rator rands ...) (x . y)) ;; improper list, inductive case
(build-apply (rator rands ... x) y))
((build-apply (rator rands ...) rest-args) ;; improper list, base case
(apply rator rands ... rest-args))))
The order of all three clauses above is very significant.
With that in place, here is the new form of my generator macro(s):
;; (generator ARGS BODY ...) => procedure
;; in whose body yield is bound to coroutine with caller
(define-syntax generator
(transformer
(lambda (exp ren cmp)
`(,(ren 'generator-via) yield ,(cadr exp) ,@(cddr exp)))))
(define-syntax generator-via
(syntax-rules ()
((generator-via yield-id args body ...)
(let ((get-next #f))
(lambda args
(call-with-current-continuation
(lambda (exit)
(cond
(get-next (build-apply (get-next exit) args))
(else (let ((yield-id
(lambda results
(call-with-current-continuation
(lambda (next)
(set! get-next (lambda (new-exit . args)
(set! exit new-exit)
(build-apply (next) args)))
(apply exit results))))))
(call-with-values (lambda () body ...)
;; below could also use build-apply with
;; args, but then the "done-value"(s)
;; returned from a generator would be forced
;; to have value-count = length args
(lambda argl
(set! get-next (lambda (new-exit . args)
(apply new-exit argl)))
(apply exit argl)))))))))))))
No comments:
Post a Comment