On this page:
7.1 Defining Modules
7.2 Macros and Modules
7.3 Exercises
7.4.0.4

7 Modules

We have used predefined modules like racket/base and syntax/parse. Also, every Racket program that starts #lang is a module. But we have not yet written programs that have multiple of our own modules.

7.1 Defining Modules

Let’s move our run-program function into its own module. While we’re at it, let change the name to just run. The run function expects strings, so

(run "ls" "-l")

lists the content of the current directory in long format.

Here’s the module:

"run.rkt"

#lang racket/base
(require racket/system)
 
(provide run)
 
(define (run prog . args)
  (apply system* (find-program prog) args))
 
(define (find-program str)
  (or (find-executable-path str)
      (error 'pfsh "could not find program: ~a" str)))

This module defines run and uses the provide form to export it for use by other modules. The module also defines a find-program helper function, but that function is not exported for external use.

To use run, another module imports it with require. Assuming that "use-run.rkt" is in the same directory, we can reference the "run.rkt" module using a relative path:

Windows users: try (run "cmd.exe" "/c" "dir"), instead.

"use-run.rkt"

#lang racket
(require "run.rkt")
 
(run "ls" "-l")

7.2 Macros and Modules

With the run function in its own module "run.rkt", we can implement the run macro in a separate module. Using the same name for a function and a macro is not usually a good idea, but for example purposes, it illustrates the way that layers of languages sometimes need to implement one construct in terms of a lower-level construct that has the same name.

We do have to pick a different module name, though, so let’s put the run macro in "pfsh-run.rkt" (where “pfsh” is short for “parenthesis-friendly shell” and is pronounced the same as “fish”):

"pfsh-run.rkt"

#lang racket/base
(require "run.rkt"
         (for-syntax racket/base
                     syntax/parse))
 
(provide (rename-out [pfsh:run run]))
 
(define-syntax (pfsh:run stx)
  (syntax-parse stx
    [(_ prog:id arg:id ...)
     #'(run (symbol->string 'prog) (symbol->string 'arg) ...)]))

"use-pfsh-run.rkt"

#lang racket/base
(require "pfsh-run.rkt")
 
(run ls -l)

Note how "pfsh-run.rkt" defines pfsh:run but renames it to run when exporting via provide. That way, the implementation of the macro can refer to the run function imported from "run.rkt". The macro system manages bindings properly to ensure that run in the expansion of pfsh:run will always refer to the run function, even if pfsh:run is used in a module like "use-pfsh-run.rkt" where run does not refer to the function.

7.3 Exercises

  1. Put your define-five macro in a module that exports it as just define. When you require that module into a #lang racket/base module, the required define shadows the initial define binding.

  2. Adjust your define-five macro so that (define id expr) defines id as a constant 5 and (define (id) expr) defines id as a function that returns five, but no other form is allowed (e.g., a function that takes some arguments is not allowed.

  3. The most obvious solution to the preceding exercise involves two different pattern clauses, but with essentially the same template. Try using define-syntax-class (consult the documentation) to avoid that redundancy. Note that define-syntax-class usually goes inside begin-for-syntax:

    (define-for-syntax
     (define-syntax-class
       ....))