require vs load vs include vs import in Racket
Asked Answered
P

1

12

The Racket docs indicate that Racket has separate forms for: require, load, include, and import. Many other languages only contain one of these and are generally used synonymously (although obviously with language specific differences such as #include in C, and import in Java).

Since Racket has all four of these, what is the difference between each one and which should I be using in general? Also if each has a specific use, when should I use an alternative type? Also, this question seems to indicate that require (paired with provide) is preferred, why?

Pertinacious answered 31/1, 2018 at 18:4 Comment(0)
P
13

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.

Pertinacious answered 31/1, 2018 at 19:11 Comment(2)
A mentionable difference between require compared to the RNRS import is that require will automatically shadow earlier language defined or previously required bindings. eg. #lang racket/base has map 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
Good point. require will automatically shadow bindings from the #lang (module langauge). However it will not shadow previous required bindings (unless you're in a repl, then it will.) Also local definitions will similarly automatically shadow require definitions.Pertinacious

© 2022 - 2024 — McMap. All rights reserved.