For a long time now, when I've wanted an "anaphoric macro" I've used syntax parameters. Consider a library that defines a define/return
macro for defining functions that can be short circuited using return
. The way I'd normally implement that is like this:
(provide define/return
return)
(define-syntax-parameter return
(λ (stx)
(raise-syntax-error 'return "must be used inside define/return" stx)))
(define-syntax-parse-rule (define/return header:expr body:expr ...+)
(define header
(let/ec escape
(syntax-parameterize ([return (make-rename-transformer #'escape)])
body ...))))
This works, but it means that return
is exposed as a syntax parameter to clients of the library. Someone could do this:
(require "my-awesome-return-library.rkt")
(define (explode)
(error "everything exploded"))
(define/return (foo)
(syntax-parameterize ([return (make-rename-transformer #'explode)])
(return)))
This fails with the error everything exploded
, which is a bit weird. I'm not sure clients should be allowed to redefine return
out from under themselves like that.
The define/return
library can put in a little work to prevent this however. The key lies in the observation that only the state passed from define/return
to return
needs to be a syntax parameter, not the entire return
form. So the library could do this:
(provide define/return
return)
(define-syntax-parameter return-continuation #false)
(define-syntax-parse-rule (return expr:expr ...)
#:fail-unless (syntax-parameter-value #'return-continuation)
"must be used inside define/return"
#:with escape (syntax-parameter-value #'return-continuation)
(escape expr ...))
(define-syntax-parse-rule (define/return header:expr body:expr ...+)
(define header
(let/ec escape
(syntax-parameterize ([return-continuation #'escape])
body ...))))
And now clients can't redefine return
. The syntax parameter used to communicate between define/return
and return
is fully encapsulated.
Lately I've come around to believing that this is a Good Idea™ and maybe even a Best Practice™. Does anyone else do this?