So far in the course, we have been writing small programs that are an amalgamation of mostly unrelated functions, all placed in a single file. For larger programs, this approach does not scale up particularly well. We have placed different parts of a complex computation into different functions to delineate multiple steps in the overall calculation. What is the more coarse grained way to divide up different parts of our code (combinations of functions) that play different roles in our overall program? They should be placed in different modules of code, and stored in different files.
There are various benefits to modularity. Grouping related code into dedicated files makes code browsing easier and is a stylistic benefit. The other main benefit, and arguably the primary one, is that the same module can be used with different programs. Without modules, a set of desired functions must be carefully excised from a larger file each time it is to be integrated into a new application; with modules, it simply remains in the file where it has been all along and need only be, in a sense, plugged into its new program. A third benefit of this approach is that only a single copy of the shared code is maintained, rather than several copied-and-pasted versions which may begin to diverge, and require separate maintenance.
You have already interacted with the module system. The racket-tests
module provides
features like check-expect
; the cs151-core
and related image
and universe
modules smooth sharp corners off of certain language features. But in this section, we
describe how to define your own modules to improve the organization of larger programs.
Fortunately, it is not particularly difficult to interact with Racket’s module system.
To create a module, you place related type definitions (define-type
and define-struct
forms) and functions into one .rkt
file. You then determine which parts of the code
need to be accessible outside of that file. For instance, you might make your helper functions
private — internal to your module — while making the rest of your code externally
accessible.
Defining modules
To make a define-type
definition externally visible, add a provide
directive below it,
as in the following example:
(define-type Suit (U 'Spades 'Hearts 'Clubs 'Diamonds))
(provide Suit)
Making a function accessible to code outside the module is similar.
(: red? : Suit -> Boolean)
(define (red? s)
(or (symbol=? s 'Hearts) (symbol=? s 'Diamonds)))
(provide red?)
To make a struct
externally available requires some additional syntax.
(define-struct Point2D
([x : Real]
[y : Real]))
(provide (struct-out Point2D))
Using the struct-out
modifier causes all of the synthesized operations to also
be exported from the module (e.g., Point2D-x
and Point2D-y
). If we just
wrote
(provide (struct-out Point2D))
then the Point2D
type would be available, but it would be abstract.
It is also possible to rename types and operations that are provided
in a module. For example, the following code provides the function
red-card?
to clients of the module, where the implementation of the
function is named red?
inside the module.
(provide (rename-out [red? red-card?]))
At the top of each module file, indicate the language (#lang typed/racket
) and require
any other modules (such as cs151-core
) that the code in the file needs.
While we have put the provide
directives directly following the definitions in
these examples, in practice you may want to collect all of the directives together at
the bottom of the file so that it is easy to see what definitions are being exported.
Using modules
In any other file that needs to have access to the types, structs, and/or functions declared
in a module, require the module. For instance, if the file point.rkt
contains the Point2D
type and routines for manipulating two-dimensional points, then a file that works with
shapes might have:
(require "point.rkt")
alongside its other require directives. It can then access the types and functions defined
in the other file as if they were part of the same file (as long as they were made externally
visible using provide
).