Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between visiting, instantiating, and declaring a module?

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?

like image 970
Leif Andersen Avatar asked Sep 17 '25 21:09

Leif Andersen


1 Answers

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:

  1. Module visiting is running phase level 1 (macro/compile time code), and not running phase level 0 (run time code).

  2. 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.

like image 82
Leif Andersen Avatar answered Sep 22 '25 04:09

Leif Andersen