Friday, February 11, 2005

Modules and shadowing

In the lambda calculus, you can simulate modules with let; you just stick all your modules in one big let expression. But one of the major differences between modules and ordinary functions is that you don't want to allow shadowing between modules, because when you require a bunch of modules, you require them at the same level of nesting, whereas nesting is always apparent in the lambda calculus.

PLT Scheme almost gets this right; you can't require two modules that export the same name without explicitly resolving the conflict (either by excluding one of the conflicting bindings, or by prefixing all bindings imported from one of the modules).

But this isn't true for the literals list in a macro definition. The patterns in a macro definition match a subexpression of a macro invocation not against the symbolic name of a syntactic literal, but against its binding. This way even keywords have lexical scope. So if you shadow the binding of a particular identifier, you can no longer use it as a literal for a macro. For example:
(let ([else #f))
(cond
[#f 'false]
[else 'else]))
falls off the end of the cond expression, because the keyword else does not have the same binding as it did in the lexical context of the macro definition.

This is the right behavior for ordinary lexical scope, but not for modules. What you want is for a module to export its keyword names as well -- this will force the client module to resolve conflicts explicitly just as with ordinary identifiers.

Otherwise you get annoying errors like this: requiring SRFI-1, which exports a procedure any (which is essentially ormap), and the standard contract.ss library, which has several macros that allow a special literal any, representing the trivial contract, causes the SRFI's binding of any to shadow the literal's binding. Writing contracts with any then cause very confusing errors:
->: expected contract or procedure of arity 1, got #<procedure:any>
Ryan has suggested that authors of macros with non-empty literals lists should export the literal names as well by hand. Here's a macro that does essentially that:
(module keywords mzscheme
(define-syntax provide-keywords
(syntax-rules ()
[(_ name ...)
(begin
(begin
(define-syntax (name stx)
(raise-syntax-error
'name
"cannot directly use literal keyword"
stx))
(provide name))
...)]))
(provide provide-keywords))
The macro writer can then implement a macro, export it as usual, and then export its literals, like so:
(provide cond)
(provide-keywords else)
Then clients that import a module that tries to bind else will be forced to resolve the contract explicitly. (And trying to use the keyword outside the context of the macro it was intended for results in a syntax error.)

No comments: