1. Require
You are correct, the default one you want is almost always require
(paired with a provide
). These two forms go hand in hand with Racket's modules
and allows you to more easily determine what variables should be scoped in which files. For example, the following file defines three variables, but only exports 2.
#lang racket ; a.rkt
(provide b c)
(define a 1)
(define b 2)
(define c 3)
As per the Racket style guide, the provide should ideally be the first form in your file after the #lang
so that you can easily tell what a module provides. There are a few cases where this isn't possible, but you probably won't encounter those until you start making your own Racket libraries you intend for public distribution. Personally, I still put a file's require
before its provide
, but I do sometimes get flack for it.
In a repl, or another module, you can now require this file and see the variables it provides.
Welcome to Racket v6.12.
> (require "a.rkt")
> c
3
> b
2
> a
; a: undefined;
; cannot reference undefined identifier
; [,bt for context]
There are ways to get around this, but this serves as a way for a module to communicate what its explicit exports are.
2. Load
This is a more dynamic variant of require. In general you should not use it, and instead use dynamic-require
when you need to load a module dynamically. In this case, load
is effectively a primitive that require
uses behind the scenes. If you are explicitly looking to emulate the top level however (which, to be clear, you almost never do), then load is a fine option. Although in those rare cases, I would still direct you to the racket/load
language. Which interacts exactly as if each form was entered into the repl directly.
#lang racket/load
(define x 5)
(displayln x) ; => prints 5
(define x 6)
(displayln x) ; => prints 6
3. Include
Include is similar to #include
in C. There are even fewer cases where you should use it. The include
form grabs the s-expression syntax of the given path, and puts it directly in the file where the include
form was. At first, this can appear as a nice solution to allow you to split up a single module into multiple files, or to have a module 'piece' you want to put in multiple files. There are however better ways to do both of those things without using include
, that also don't come with the confusing side effects you get with include.1 One thing to remember if you still insist on using import
, is that the file you are importing probably shouldn't have a #lang
line unless you are explicitly wanting to embed a submodule. (In which case you will also need to have a require
form in addition to the include
).
4. Import
Finally, import
is not actually a core part of Racket, but is part of its unit system. Units are similar in some ways to modules, but allow for circular dependencies (unit A can depend on Unit B while Unit B depends on Unit A). They have fallen out of favor in recent years due to the syntactic overhead they have.
Also unlike the other forms import
(and additionally export
), take signatures, and relies on an external linker to decide which actual units should get linked together. Units are themselves a complex topic and deserve their own question on how to create and link them.
Conclusion (tldr)
TLDR; Use require
and provide
. They are the best supported and easiest to understand. The other forms do have their uses, but should be thought of 'advanced uses' only.
1These side effects are the same as you would expect for #include
in C. Such as order being important, and also with expressions getting intermixed in very unpredictable ways.
require
compared to the RNRSimport
is thatrequire
will automatically shadow earlier language defined or previously required bindings. eg.#lang racket/base
hasmap
that does not allow different sized lists to be passed while(require srfi/1)
will overwrite it with a different implementation that stop at the shortest list. In RNRS you need to exclude one since importing same from two sources is considered an error. – Nepali