[plt-scheme] Reproviding "else"
On Mar 16, 2009, at 3:19 PM, Neil Toronto wrote:
> The attached example language module "cond-lang.ss" implements a
> very small language, reproviding a few bits of Scheme and providing
> a new cond macro. When "cond-test.ss" uses that macro, the compiler
> gives an error:
>
> compile: unbound identifier (and no #%top syntax transformer
> is bound) in: else
>
> It's not just else, though: you can't use as a literal any name
> that's in scope in the macro definition. (For example, the compiler
> gives the same error if else is changed to foobar and foobar is
> defined at the top level.) Well, you can, but you have to provide
> that name as well as the macro. If you can't safely do that, there
> are uglier ways.
>
> Why are literals with syntax properties used for matching?
Rather, the literals are matched by *binding*, not by their symbolic
name. The macro is looking for the 'else' in scope in "cond-lang.ss",
which is the 'else' from the 'scheme' language. But the 'else' used in
"cond-test.ss" is something else, so the macro interprets it as a
'text-expr^', and it eventually gets flagged as an unbound variable.
The solution, as you remark, is to provide 'else'.
So, why are literals matched by binding, rather than by symbolic name?
It's a matter of syntax design. Your macro analyzes its clauses and
tries to distinguish between an else-clause and an ordinary test-
clause. In order for the macro to distinguish the two kinds of
clauses, they should not overlap. That is, there shouldn't be any
valid test-clause that is also an else-clause. They have the same
shape, so the thing that really distinguishes them is the first part:
There shouldn't be any valid test expression that is also the else
keyword.
If keywords were recognized by just their spelling, then there would
be an overlap between the else keyword and a local variable called
'else' in the "cond-test.ss" module. Instead, the 'else' keyword is
defined as a macro that always raises an error when used as an
expression. That makes 'else' effectively not a valid expression, so
there's no overlap.
The alternative would be to say that a test-expression is almost like
an ordinary expression, except that it can't be a variable named
'else'. This might seem innocuous---who uses 'else' as a variable
name? But it's a hidden danger and, at the same time, unnecessary
clutter in the meaning of 'new:cond'. Better to *reserve* the word
right off the bat and learn of conflicts as early as possible.
There are other advantages that come from treating keywords as
bindings, including the ability to rename them when you import them
into a module, the ability to keep some keywords internal (just don't
export them), and the ability to get nice documentation links for
keywords via Scribble :)
Ryan
> #lang scheme
>
> (define-syntax new:cond
> (syntax-rules (else)
> [(_) (void)]
> [(_ [else else-expr]) else-expr]
> [(_ [test-expr^ then-expr^] [test-expr then-expr] ...)
> (if test-expr^
> then-expr^
> (new:cond [test-expr then-expr] ...))]))
>
> (provide
> #%module-begin #%datum #%app if define
> (rename-out [new:cond cond])
> < quote)
> #lang s-exp "cond-lang.ss"
>
> (define (classify-age n)
> (cond [(< n 0) 'twinkle-in-daddys-eye]
> [(< n 10) 'precocious]
> [(< n 20) 'cheeky]
> [(< n 30) 'spunky]
> [(< n 50) 'not-dead-yet]
> [(< n 120) 'cobol-programming]
> [else 'holy-fate-worse-than-death-batman!]))
>
> (classify-age 121)
> _________________________________________________
> For list-related administrative tasks:
> http://list.cs.brown.edu/mailman/listinfo/plt-scheme