Why modules and when?

I'm working on a project in racket—a web app—and I'm wondering whether to make my individual rkt files into modules so the can be imported as, e.g.,(require users), or just leave them as is so they'd be imported as, e.g., (require "users.rkt"). Are there any benefits to either choice, or is it a question of taste or style?

1 Like

There seems to be a confusion regarding what a “module” is. Essentially, every file that starts with a #lang line is a module. The difference between the two module paths "some-module.rkt" and some-collection/some-module is that the former is a relative import, while the latter is a library import. The convention is that relative imports are used for package-internal imports, whereas library imports for package-external ones. As a library writer, you should expose useful exports in public modules (preferably under a collection to avoid potential conflict) so that clients can import them as libraries.

7 Likes

So if I'm reading this correctly, there's no real downside to sticking with relative imports if I'm building an app and I have no expectation that anyone will use it as a library, but if I were building a library for other programmers, then I should use library imports

Is that about right?

That’s not what @usao meant.

When you use stuff from your current project, use relative import. When you use stuff from other projects, use library import. This applies to both applications and libraries.

For example, let’s say I am building an app with the main file app.rkt. The file depends on helper.rkt in the same project, and /path/to/another-project/easy-xml/transform.rkt. In that case, your app.rkt would have:

(require "helper.rkt"
         easy-xml/transform)

provided that you installed /path/to/another-project/easy-xml so that the library import easy-xml/transform works.

If that is what @usao meant, then I didn't phrase my question correctly. Actually, it would be better to say that my question was based on a faulty presupposition. The resulting confusion made me go back and read the docs more carefully, and I think I understand where I went wrong now. Allow me to explain.

Suppose I am writing an app consisting of two files in a directory like so:

my-app/
├─ main.rkt
├─ helper.rkt

I was trying to figure out what I would have to to helper.rkt so that I could use a library import in main.rkti.e., (require helper) or (require my-app/helper)—rather than a relative import. I had thought that this could be accomplished by use of the module form in helper.rkt.

Based on my closer reading, my thinking was incorrect. If I understand correctly, in order to import a module using a library import, it is necessary—though maybe not sufficient—for that module to be in the proper collection directories—e.g. /usr/share/racket/collects. As such, it is practically impossible, in the app described above, to import helper.rkt into main.rkt using a library import.

Is this roughly correct?

I think you're a lot closer, yes.
(1) It's standard to just use relative imports to import files that are part of the same package, that's not worse than importing them using a package-relative spec.
(2) It's probably easier than you think to get your directory to be a package that can be imported from other places; it's generally as simple as putting a little stub info.rkt file in your directory (and indeed, I think you may even be able to skip adding the info.rkt), and then installing your directory as a package with e.g.

raco pkg install ./my-app

... from the parent directory.

EDIT: the point here is that you definitely don't have to move your directory to be in the "proper collection directories."

1 Like