Leif Andersen
Leif Andersen

Reputation: 22332

Dynamically require a phase 1 (for-syntax) variable in Racket

Let's say I have some module foo.rkt that provides x at phase 1.

#lang racket
(begin-for-syntax
  (define x 5)
  (provide x))

When you run (module->exports "foo.rkt") you get back ((1 (x ()))), meaning that x is provided at phase 1 and no other bindings are provided.

Now, in another module I could statically import x during run time using for-template:

#lang racket
(require (for-template "foo.rkt"))
x ; => 5

But this is static, and so it will always happen.

If this was at phase 0 I could use dynamic-require. But it seems like you can only use dynamic-require to run phase 1 code, not get any values from that ran code.

There is also dynamic-require-for-syntax, but I could never manage to work.

Finally, there's also namespace-require, but then it brings it into the namespace's phase 1, rather than phase 0. So I could do something like (eval '(begin-for-syntax (writeln x)), but that will only print the value of x, not return it.

There's also namespace-variable-value, but it also only seems to return values at phase 0.

So, is there anyway that I can dynamically (not statically) import a phase 1 variable from a module?

Upvotes: 2

Views: 204

Answers (1)

Leif Andersen
Leif Andersen

Reputation: 22332

Yes there is a way, but its kind of disgusting.

First of all, we need to make a base namespace, so something like (define ns (make-base-namespace)) will do the trick.

Next, I would actually recommend using namespace-require/expansion-time rather than namespace-require. It will only instantiate the module (aka only run phase 1 code).

Doing this, x is not imported into the namespace, but at phase 1, so we can write a macro to 'smuggle' it from phase 1 to phase 0 through 3d syntax.

The macro is going to look something like:

(eval '(define-syntax (cheater-x stx)
         #`'#,(datum->syntax #f x)))

And now you can just do (eval 'cheater-x) to get the value of x.

Overall your code should look something like this:

(define (dynamic-require-from-syntax module binding)
  (define ns (make-base-namespace))
  (parameterize ([current-namespace ns])
    (namespace-require 'racket)
    (namespace-require/expansion-time module)
    (eval `(define-syntax (cheater-x stx)
              #`'#,(datum->syntax #f ,binding)))
    (eval 'cheater-x)))
(dynamic-require-from-syntax "foo.rkt" 'x) ; => 5

Obviously you could set up this function to use the same namespace on multiple calls so that it doesn't re-instantiate the module every time you call it. But that's a different answer.

Upvotes: 2

Related Questions