6.18.9 Declarative Modules

The first-class access to modules and module variables described in the previous subsection is very powerful and allows Guile users to build many tools to dynamically learn things about their Guile systems. However, as Scheme godparent Mathias Felleisen wrote in “On the Expressive Power of Programming Languages”, a more expressive language is necessarily harder to reason about. There are transformations that Guile’s compiler would like to make which can’t be done if every top-level definition is subject to mutation at any time.

Consider this module:

(define-module (boxes)
  #:export (make-box box-ref box-set! box-swap!))

(define (make-box x) (list x))
(define (box-ref box) (car box))
(define (box-set! box x) (set-car! box x))
(define (box-swap! box x)
  (let ((y (box-ref box)))
    (box-set! box x)
    y))

Ideally you’d like for the box-ref in box-swap! to be inlined to car. Guile’s compiler can do this, but only if it knows that box-ref’s definition is what it appears to be in the text. However, in the general case it could be that a programmer could reach into the (boxes) module at any time and change the value of box-ref.

To allow Guile to reason about the values of top-levels from a module, a module can be marked as declarative. This flag applies only to the subset of top-level definitions that are themselves declarative: those that are defined within the compilation unit, and not assigned (set!) or redefined within the compilation unit.

To explicitly mark a module as being declarative, pass the #:declarative? keyword argument when declaring a module:

(define-module (boxes)
  #:export (make-box box-ref box-set! box-swap!)
  #:declarative? #t)

By default, modules are compiled declaratively if the user-modules-declarative? parameter is true when the module is compiled.

Scheme Parameter: user-modules-declarative?

A boolean indicating whether definitions in modules created by define-module or implicitly as part of a compilation unit without an explicit module can be treated as declarative.

Because it’s usually what you want, the default value of user-modules-declarative? is #t.

Should I Mark My Module As Declarative?

In the vast majority of use cases, declarative modules are what you want. However, there are exceptions.

Consider the (boxes) module above. Let’s say you want to be able to go in and change the definition of box-set! at run-time:

scheme@(guile-user)> (use-modules (boxes))
scheme@(guile-user)> ,module boxes
scheme@(boxes)> (define (box-set! x y) (set-car! x (pk y)))

However, considering that (boxes) is a declarative module, it could be that box-swap! inlined the call to box-set! – so it may be that you are surprised if you call (box-swap! x y) and you don’t see the new definition being used. (Note, however, that Guile has no guarantees about what definitions its compiler will or will not inline.)

If you want to allow the definition of box-set! to be changed and to have all of its uses updated, then probably the best option is to edit the module and reload the whole thing:

scheme@(guile-user)> ,reload (boxes)

The advantage of the reloading approach is that you maintain the optimizations that declarative modules enable, while also being able to live-update the code. If the module keeps precious program state, those definitions can be marked as define-once to prevent reloads from overwriting them. See Top Level Variable Definitions, for more on define-once. Incidentally, define-once also prevents declarative-definition optimizations, so if there’s a limited subset of redefinable bindings, define-once could be an interesting tool to mark those definitions as works-in-progress for interactive program development.

To users, whether a module is declarative or not is mostly immaterial: besides normal use via use-modules, users can reference and redefine public or private bindings programmatically or interactively. The only difference is that changing a declarative definition may not change all of its uses. If this use-case is important to you, and if reloading whole modules is insufficient, then you can mark all definitions in a module as non-declarative by adding #:declarative? #f to the module definition.

The default of whether modules are declarative or not can be controlled via the (user-modules-declarative?) parameter mentioned above, but care should be taken to set this parameter when the modules are compiled, e.g. via (eval-when (expand) (user-modules-declarative? #f)). See Eval-when.

Alternately you can prevent declarative-definition optimizations by compiling at the -O1 optimization level instead of the default -O2, or via explicitly passing -Ono-letrectify to the guild compile invocation. See Compiling Scheme Code, for more on compiler options.

One final note. Currently, definitions from declarative modules can only be inlined within the module they are defined in, and within a compilation unit. This may change in the future to allow Guile to inline imported declarative definitions as well (cross-module inlining). To Guile, whether a definition is inlinable or not is a property of the definition, not its use. We hope to improve compiler tooling in the future to allow the user to identify definitions that are out of date when a declarative binding is redefined.