I'm trying to write in Racket a module meta-language mylang
, which accepts a second language to which is passes the modified body, such that:
(module foo mylang typed/racket body)
is equivalent to:
(module foo typed/racket transformed-body)
where the typed/racket
part can be replaced with any other module language, of course.
I attempted a simple version which leaves the body unchanged. It works fine on the command-line, but gives the following error when run in DrRacket:
/usr/share/racket/pkgs/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt:479:30: require: namespace mismatch;
reference to a module that is not available
reference phase: 1
referenced module: "/usr/share/racket/pkgs/typed-racket-lib/typed-racket/env/env-req.rkt"
referenced phase level: 0 in: add-mod!
Here's the whole code:
#lang racket
(module mylang racket
(provide (rename-out [-#%module-begin #%module-begin]))
(require (for-syntax syntax/strip-context))
(define-syntax (-#%module-begin stx)
(syntax-case stx ()
[(_ lng . rest)
(let ([lng-sym (syntax-e #'lng)])
(namespace-require `(for-meta -1 ,lng-sym))
(with-syntax ([mb (namespace-symbol->identifier '#%module-begin)])
#`(mb . #,(replace-context #'mb #'rest))))])))
(module foo (submod ".." mylang) typed/racket/base
(ann (+ 1) Number))
(require 'foo)
Requirements (i.e. solutions I'd rather avoid):
- Adding a
(require (only-in typed/racket))
inside themylang
module makes this work, but I'm interested in a general solution, wheremylang
does not need to know abouttyped/racket
at al (i.e. if somebody adds a new languagefoo
, thenmylang
should work with it out of the box). Also, I'm not interested in tricks which declare a submodule and immediately
require
and re-provide
it, as is done here, because this changes the path to the actual module (somain
andtest
loose their special behaviour, for example).It is also slower at compile-time, as submodules get visited and/or instantiated more times (this can be seen by writing
(begin-for-syntax (displayln 'here))
, and has a noticeable impact for largetyped/racket
programs.Bonus points if the arrows in DrRacket work for built-ins provided by the delegated-to language, e.g. have arrows from
ann
,+
andNumber
totyped/racket/base
, in the example above.
One thing you can do, which I don't think violates your requirements, is put it in a module, fully expand that module, and then match on the
#%plain-module-begin
to insert a require.And if you wanted to transform the body in some way, you could do that either before or after expansion.
The pattern-matching to insert the extra
(#%require lng)
is necessary because expanding the module body in a context wherelng
is available isn't enough. Taking themod-body
code back out of themodule
form means that the bindings will refer tolng
, butlng
won't be available at run-time. That's why I get therequire: namespace mismatch; reference to a module that is not available
error without it, and that's why it needs to be added after expansion.Update from comments
However, as @GeorgesDupéron pointed out in a comment, this introduces another problem. If
lng
provides an identifierx
and the module where it is used imports a differentx
, there will be an import conflict where there shouldn't be. Require lines should be in a "nested scope" with respect to the module language so that they can shadow identifiers likex
here.@GeorgesDupéron found a solution to this problem in this email on the racket users list, using
(make-syntax-introducer)
on themod-body
to produce the nested scope.