Previous value a syntax parameter
The block
form from ISLisp
Recently I came across the specification of ISLisp.
What Scheme is to Racket, ISLisp is to Common Lisp.
That is, the ISLisp specification is closer in style and size to RnRS
than it is to the Common Lisp specification.
The construct block
from ISLisp is like the Racket block
but
also supports non-local exit. It is close to let/ec
but the
interface is different.
The ISLisp specification of block
is as follows:
---
`(block name form*) -> <object>`
`(return-from name result-form)` *transfers control and data*
---
The `block` special form executes each `form` sequentially from left
to right. If the last `form` exits normally, whatever it returns is
returned by the `block` form.
The `name` in a `block` form is not evaluated; it must be an
identifier. The scope of `name` is the body `form*` — only a
`return-from` textually contained in some `form` can exit the
block. The extent of `name` is dynamic.
If a `return-from` is executed, the `result-form` is evaluated. If
this evaluation returns normally, the value it returns is immediately
returned from the innermost lexically enclosing `block` form with the
same `name`.
`return-from` is used to return from a `block`. `name` is not
evaluated and must be an identifier. A `block` special form must
lexically enclose the occurrence of `return-from`; the value produced
by `result-form` is immediately returned from the block. The
`return-from` form never returns and does not have a value.
Implementing block
in Racket
We will call our extension of block
for block*
.
A simple version of block*
is short:
(define-syntax (block* stx)
(syntax-parse stx
[(_block* name form ...)
(syntax/loc stx
(let/ec name-k
(syntax-parameterize
([return-from (syntax-rules (name)
[(_return-from name e) (name-k e)])])
(block form ...))))]))
The expansion of block*
binds name-k
to a procedure we can
call later to exit from the block. If one of the forms
in (block form ...)
is (return-from name e)
it must expand
into (name-k e)
. In order to adjust the meaning of return-from
we use syntax-parameterize
and bind return-from
to the
transformer:
(syntax-rules (name)
[(_return-from name e) (name-k e)])
Since name
occurs in the list of literals, the pattern
(_return-from name e)
only matches the name of block.
At this point we have a block*
that works like this:
> (block* here 'a (return-from here 'b) 'c)
c
> (block* here 'a (return-from not-here 'b) 'c)
... an error ...
The problem of nested block*
expressions
The simple version of block*
has a problem with nested uses.
Our expansion works only for a nested block*-expression, if
it uses return-from
with the inner-most name.
> (block* there (list (block* here (return-from here 'a))))
'(a) ; okay
> (block* there (list (block* here (return-from there 'a))))
'(a) ; oops, should have been 'a
The culprit is the rule:
(syntax-rules (name)
[(_return-from name e) (name-k e)])
The rule only matches the inner-most block name. We need it to match outer
names too. If we could use the name old-return-from
for the transformer
for return-from
before the new syntax parameterization kicks in, then
we could write:
(syntax-rules (name)
[(_return-from name e) (name-k e)]
[(_return-from outer-name e) (old-return-from outer-name e)]
An attempt of getting this idea to work failed:
(define-syntax-parameter old-return-from (syntax-rules ()))
(define-syntax (block* stx)
(syntax-parse stx
[(_block* name form ...)
(syntax/loc stx
(let/ec name-k
(syntax-parameterize
([old-return-from (syntax-rules ()
[(_old-return-from to-name e) (return-from to-name e)])])
(syntax-parameterize
([return-from (syntax-rules (name)
[(_return-from name e) (name-k e)]
[(_return-from to-name e) (old-return-from to-name e)])])
(block form ...)))))]))
The reason this fails is that the expansion of old-return-from
expands into a use
of the syntax-parameter return-from
. The inner syntax-parameterize
takes effect before
the return-from
form produced by old-return-from
is expanded. This leads to an
infinite expansion loop.
Other attempts of getting hands of the previous transformer for return-from
and using it to define a new version of return-from
failed.
Question
How can I use a previous transformer for a syntax parameter to define a new?
A different solution
The solution below introduces a helper form do-block*
(do-block* ((name name-k) ...) form ...))
It works like block*
but the first subform is a list
of pairs of block names and associated exit procedures.
Since a nested block*
needs to pass along the current list
of block names and exit procedures, block*
was made into a
syntax parameter.
#lang racket/base
(require (for-syntax racket/base)
racket/stxparam
racket/block)
(define-syntax-parameter return-from
(syntax-rules ()
[(_ . _) (error 'return-from "used outside block* form")]))
(define-syntax-parameter block*
(syntax-rules ()
[(_block* name form ...)
(let/ec name-k
(do-block* ((name name-k)) form ...))]))
(define-syntax do-block*
(syntax-rules ()
[(_do-block* ((name name-k) ...) form ...)
(syntax-parameterize
([return-from (syntax-rules (name ...)
[(_return-from name e) (name-k e)]
...)]
[block* (syntax-rules ()
[(_block* -name -form (... ...))
(let/ec -name-k
(do-block* ((-name -name-k) (name name-k) ...)
-form (... ...)))])])
(block form ...))]))
(list
(equal? 'a (block* here 'a))
(equal? 'b (block* here 'a 'b))
(equal? 'b (block* there 'a (block* here 'b)))
(equal? 'c (block* there 'a (block* here 'b) 'c))
(equal? 'b (block* here 'a (return-from here 'b) 'c))
(equal? '(a) (block* there (list (block* here (return-from here 'a) 'b))))
(equal? 'a (block* there (list (block* here (return-from there 'a) 'b))))
(equal? 'a (block* here (return-from here 'a) (define b (/ 0 0))))
(equal? 'a (block* here (return-from here 'a) (error)))
(equal? 'a (block
(define (f1)
(block* b
(let ([f (λ () (return-from b 'a))])
'big-computation
(f2 f))))
(define (f2 g)
'another-big-computation
(g))
(f1))))