3.26 Importing Modules Lazily: lazy-require (original) (raw)

3.26 Importing Modules Lazily: lazy-require🔗

(lazy-require [module-path (fun-import ...)] ...)
fun-import = fun-id | (orig-fun-id fun-id)

Defines each fun-id as a function that, when called, dynamically requires the export named orig-fun-id from the module specified by module-path and calls it with the same arguments. If orig-fun-id is not given, it defaults tofun-id.

If the enclosing relative phase level is not 0, thenmodule-path is also placed in a submodule (with a use ofdefine-runtime-module-path-index at phase level 0 within the submodule). Introduced submodules have the nameslazy-require-auxn-m, wheren is a phase-level number and m is a number.

When the use of a lazily-required function triggers module loading, it also triggers a use of register-external-module to declare an indirect compilation dependency (in case the function is used in the process of compiling a module).

Examples:

> (partition even? '(1 2 3 4 5))
> (lazy-require ['hello ([hello greet])])
> (greet)
starting hello serverhello!
(lazy-require-syntax [module-path (macro-import ...)] ...)
macro-import = macro-id | (orig-macro-id macro-id)

Like lazy-require but for macros. That is, it defines eachmacro-id as a macro that, when used, dynamically loads the macro’s implementation from the given module-path. Iforig-macro-id is not given, it defaults to macro-id.

Use lazy-require-syntax in the implementation of a library with large, complicated macros to avoid a dependence from clients of the library on the macro “compilers.” Note that only macros with exceptionally large compile-time components (such as Typed Racket, which includes a type checker and optimizer) benefit fromlazy-require-syntax; typical macros do not.

Warning: lazy-require-syntax breaks the invariants that Racket’s module loader and linker rely on; these invariants normally ensure that the references in code produced by a macro are loaded before the code runs. Safe use of lazy-require-syntaxrequires a particular structure in the macro implementation. (In particular, lazy-require-syntax cannot simply be introduced in the client code.) The macro implementation must follow these rules:

  1. the interface module must require the runtime-support module
  2. the compiler module must require the runtime-support module via an absolute module path rather than a relative path

To explain the concepts of “interface, compiler, and runtime-support modules”, here is an example module that exports a macro:

Suppose we want to use lazy-require-syntax to lazily load the implementation of the ntimes macro transformer. The original module must be split into three parts:

The runtime support module contains the function and value definitions that the macro refers to. The compiler module contains the macro definition(s) themselves—the part of the code that “disappears” after compile time. The interface module lazily loads the macro transformer, but it makes sure the runtime support module is defined at run time by requiring it normally. In a larger example, of course, the runtime support and compiler may both consist of multiple modules.

Here what happens when we don’t separate the runtime support into a separate module:

> (module bad-client racket/base (require racket/lazy-require) (lazy-require-syntax ['bad-no-runtime (ntimes)]) (ntimes 3 (printf "hello?\n")))
> (require 'bad-client)
require: namespace mismatch;
reference to a module that is not instantiated
module: 'bad-no-runtime
phase: 0

A similar error occurs when the interface module doesn’t introduce a dependency on the runtime support module.