Functions in racket such as module->language-info
, module->imports
, and module->exports
expect their module to be declared, but not necessarily visited or instantiated.
Now, dynamic-require
seems to have several options on how to require modules, including both visiting and instantiating.
This made me wonder, what is the difference between declaring a module, visiting a module, and instantiating a module?
Declaring a module is just that this module is somewhere in the current namespace. This can be done both by a require, or just writing out the module literally in code.
Module visiting and instantiation is a bit more tricky to understand, and can best be described as such:
Module visiting is running phase level 1 (macro/compile time code), and not running phase level 0 (run time code).
Module instantiation is running phase level 0 code, but NOT phase level 1 code.
Now, the tricky bit is, to run phase level 0 code, you have to have previously run all of the higher level phased code, if it has not been compiled. However, if this module has already been visited (and compiled) it will not run the phase level 1 code again.
This can be seen with the following module, called test.rkt
:
#lang racket
(require (for-meta 2 racket/base))
(displayln "phase 0")
(begin-for-syntax
(displayln "phase 1")
(begin-for-syntax
(displayln "phase 2")))
This module has code that runs at phase 0, phase 1, and phase 2 (the macro expansion for macro expansion phase). In each of these phases, it prints out a line to indicate that the phase is running. Using this module, we can see when the module is getting instantiated.
Now, let's create the following file which instantiates test.rkt
:
#lang racket
(dynamic-require "test.rkt" #f)
If we run this in DrRacket, the output will look something like:
phase 2
phase 1
phase 0
Now, we see phase 0
because the module is being instantiated. In this case however, we also see phase 2
and phase 1
, because the module's syntax phase was necessarily instantiated to run the phase 0
code.
However, if you run it again (assuming you have caching of already compiled files turned on), you will get:
phase 0
In this case you only see phase 0
because the phase 1
and higher code has already been expanded. We could have also given 0
to dynamic require for similar results.
Now, if instead of passing in 0
or #f
to dynamic-require
, we passed in (void)
, giving us the following file:
#lang racket
(dynamic-require "test.rkt" (void))
Then the output will be (again, assuming you have compile caching turned on), will look like:
phase 1
This is because the macro level (phase 1) code is being run here, rather than run time (phase 0) code. Although if we make a slight change to test.rkt
and save it again (to invalidate the cache), we will get:
phase 2
phase 1
This is because Racket had to expand phase 2
code in order to be able to run the phase 1
code. Now that the compiled module cache has been updated, if you run it again, it will output only:
phase 1
Finally, there is not (as far as I can see, please update this if I'm wrong), any way to directly use dynamic-require
to guarantee that code higher than phase level 1 will be run.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With