Chapter 9 — Modules and packages
So far every program has been one file. This chapter splits a program into
modules, explains how use finds them, and adds a package someone else
wrote.
Every file is a module#
A module is just a .hy file. Its name comes from its path below the
project’s src/ directory: src/geometry.hy is the module geometry, and
src/geometry/shapes.hy is geometry::shapes. There is nothing to declare.
Here is a small project:
inventory/
├── coil.toml
└── src/
├── main.hy
├── money.hy
└── store/
└── item.hy
src/money.hy holds a function and a constant:
const CURRENCY = "EUR";
fn format_cents(int cents) -> string {
return show(cents / 100) + "." + show(cents % 100 / 10) + show(cents % 10) + " " + CURRENCY;
}
src/store/item.hy holds a class:
class Item {
pub name: string,
pub price_cents: int,
pub quantity: int,
}
impl Item {
pub fn total_cents() -> int {
return self.price_cents * self.quantity;
}
}
src/main.hy imports what it needs from both:
use io::sync::{println};
use money::{format_cents};
use store::item::{Item};
fn main() {
let lamp = new Item("lamp", 1999, 3);
println(lamp.name + ": " + format_cents(lamp.total_cents()))?;
}
Output:
lamp: 59.97 EUR
Run it with spool run from the project root, or with
coil --root ~/.coil/stdlib/src src/main.hy. The compiler looks for
modules in src/ under the directory you run it from, plus every --root.
use#
use brings names from another module into this file. The path names the
module, and the braces list what to import:
use money::{format_cents, CURRENCY};
use store::item::{Item};
use text::{Searchable, trim as strip}; // `as` renames on import
Anything declared at the top level of a module can be imported: functions,
classes, enums, traits, constants and type aliases. There is no use x::*;
listing the names keeps every file’s dependencies visible at its top.
The same rule covers the stdlib and the compiler’s built-in modules. io,
string, env and thread are built into the compiler, and text,
collections and conv are stdlib source files, but you import from both
the same way. Only the prelude is available
without a use.
How a path finds its file#
For use store::item::{Item}, the compiler tries, in each root:
store/item.hy, a file for the modulestore::item;- then
store.hy, in caseitemis something declared inside the modulestore.
The first file that exists wins. Roots are searched in order: src/
first, then each --root you pass. spool passes the stdlib and each
dependency as roots for you.
If nothing matches, the error names the import:
Error: [E0900] Module not found: `use store::itme::Item`
The modules reference has the complete rules.
Organising a project#
A few conventions keep projects easy to navigate:
- Group related code by topic (
store/item.hy,store/cart.hy) rather than by kind (classes.hy,helpers.hy). - Keep
main.hysmall: parse arguments, call into modules, report errors. - Put tests for
src/store/cart.hyintests/store/cart.hy. Test files import the code they test withuse, like any other module. - Name helpers that other modules should not rely on with a leading
__. They can still be imported, but they are left out of generated documentation.
Using a package#
A package is a git repository with Coil modules in its src/. Add one with
spool add:
spool add json --git https://github.com/ardax-corp/coil-json.git
spool records the dependency in coil.toml (the newest release tag, or the
latest commit when the package has no releases yet), pins the exact commit
in coil.lock and downloads it. The package’s modules are then importable
like your own:
use json::{Json, JsonValue};
spool update moves dependencies to the newest versions coil.toml
allows, and spool tree shows what depends on what. The
packages page lists the libraries maintained alongside Coil,
each with its own documentation.
Writing a library#
spool new mylib --lib creates a library. Its main module is
src/mylib.hy, so users write use mylib::{…}. Further modules go in
src/mylib/ and are imported as use mylib::parser::{…}.
Write a /// comment on everything others will use. The examples in those
comments run as tests (chapter 11), so they double as checked
documentation.
Exercises#
- Move
fizzbuzzfrom chapter 1 intosrc/fizz.hyand call it fromsrc/main.hy. - Add a
store/cart.hymodule with aCartclass that holds aVec<Item>and has atotal_cents()method. - Rename an import with
asto avoid a clash between two modules that both export a function calledparse.