Modules and files
One file is one module, and the path you write means the same thing from everywhere.
When a program uses several files, you need to know where the compiler looks for them and how to use the declarations they contain.
In Heroes, each file is a module. use geom/point reads geom/point.hero relative to the directory of the file you compile. The module name is the last part of the path, so its declarations use the form point.something.
Paths start at the program root
If you run heroes run shapes/main.hero, shapes/ is the program’s root directory. Every use path in every file starts there.
use geom/point
use geom/area
use render/ascii
use render/scale as near_scale
use scale
The program has six modules in three directories. Here is how a file uses a module in the same directory:
use geom/point
function rect(a: point.Point, b: point.Point) -> i64
return point.span(from: a, to: b) * height(a: a, b: b)
area.hero and point.hero are in the same directory, but area.hero still gives the path from the root. The declaration use geom/point is therefore identical in main.hero, geom/area.hero and render/ascii.hero. Moving the file that contains it within the program does not change which module it refers to.
There is no way up
No .., no leading slash, no way to leave the root. Try it and
the compiler tells you what the rule is rather than what the syntax was:
$ heroes check shapes/main.hero
error[module_path_climbs]: a `use` path cannot climb — every `use` starts at the directory of the file you compile, so write the path from there: `use lex/token`, wherever the file that writes it sits
at shapes/geom/area.hero:8:5
|
8 | use ../geom/point
| ^
note: a module above that directory belongs to a different program: give this one its own root, or move the file in
A program can use only files inside its root. To include an external file, move it inside or choose an entry file in a directory that contains all the required modules.
The name is the last part
use geom/point makes the name point available, using the last part of the path. Directory names are excluded: the name is neither geom_point nor geom.point.
corner = point.Point(x: 0, y: 0)
far = point.Point(x: 4, y: 3)
print(area.rect(a: corner, b: far))
print(area.perimeter(a: corner, b: far))
print(ascii.bar(width: point.span(from: corner, to: far)))
print(ascii.frame(width: 3, height: 2))
To use a declaration from another module, include the module name, called its qualifier. A Point type from the point module requires that prefix. Likewise, point.span(...) calls a function in the module identified by the use declaration.
Two modules can end in the same word
Both scale.hero at the root and render/scale.hero have the name scale. They can exist in one program, but a file that uses both must distinguish them:
$ heroes check shapes/main.hero
error[module_binding_taken]: this file already calls `render/scale` by the name `scale`, so `scale` cannot have it too — a file may not bind one name twice
at shapes/main.hero:23:5
|
23 | use scale
| ^^^^^
note: the two modules may both exist and both be used — `as` says what THIS file calls each one, and two files may name the same module differently
The repair is the as clause on line 22 of the file above, and with it both names are usable:
print(scale.factor())
print(near_scale.factor())
use render/scale as near_scale names the module near_scale in this file only. Another file can still call it scale. This resolves the conflict without changing the module. The example prints 10 using the root’s scale and 3 using the one in render/.
Avoiding name conflicts
use geom/point declares the name point in the file. A variable in the same file cannot use that name: it would hide the module, as explained in chapter 7:
$ heroes check shapes/main.hero
error[shadowed_binding]: `point` is what this file calls the module `geom/point`, so a local may not take it — shadowing is an error here
at shapes/main.hero:26:5
|
26 | point = point.Point(x: 0, y: 0)
| ^^^^^
note: rename the module for this file with `use geom/point as `, which clears every collision in it at once, or rename this binding
This rule prevents point.x from referring to a module in one function and a variable in another. The message suggests two options: rename the module or rename the variable.
Module access rules
There is no public or private: a module’s declarations are accessible from every file that uses it. The qualifier is always required. The compiler reports circular imports at the use line. No project or configuration file is needed: directories organise the modules, and the file passed to the command determines the root.
Next: Tests and holes, where an unfinished program is still a program the compiler will talk to you about. ← Previous: Values all the way down
Run a program of your own: it takes four commands. Or take the whole language in one file, and every program already written in it.