a kanso module is a directory. that's the whole definition. the files inside share one namespace—no import statements between them, no export lists, no manifest declaring what lives where. you split a module across files for the reader's benefit, and the compiler treats the result as one program. here is a two-file teahouse:
teahouse/menu.kso
fnbase_price"dango"350fnbase_price"taiyaki"500fnbase_price item
err"we do not serve {item}"fndescribe (err reason)
"gomen: {reason}"fndescribe n
"{n} yen, service included"pubfnprice item
base_price item +50
400 yen, service included
gomen: we do not serve pocky
point the toolchain at the directory and it runs the module. main.kso calls price from menu.kso as if it were next door, because it is. notice three earlier chapters shaking hands in a dozen lines: string-literal dispatch is the menu, an err is the missing item, and the railway carries it from base_price through price—whose + 50 never runs on the sad track—out to the arm in describe.
one namespace, many files
most languages make a file a unit of encapsulation: each file is its own scope, and getting a name from one file into another costs an import at the top and an export at the source. kanso spends none of that. the module is the unit; the files are just where you chose to put things. every top-level name in menu.kso is visible in main.kso with no ceremony, and the reverse holds too.
this is a deliberate trade. a file boundary that carried meaning would be a second thing to keep consistent with the first—two files that must agree on what crosses between them, drifting the moment someone forgets. kanso deletes the boundary and keeps the organization: file names still carry information—you named menu.kso because it holds the menu—but they name a chapter of one book, not a wall inside it. want to know what a name means? there is exactly one place it is defined, anywhere in the directory.
canonical order within a file
inside a file, declarations are not in whatever order you typed them—they're in the order. types come first, then everything else alphabetically by name. this is grammar, not advice: get it wrong and the file does not compile.
read the declaration heads top to bottom: the type receipt, then checkout, line, play, price, service. that is the order the author chose, and the compiler has nothing to say about it. definition order is not scope either: checkout calls line and line reads service, both defined below them, and it all resolves.
so put a helper beside the thing it helps, and keep the entry point wherever a reader will look for it. the two price arms sit together here because reading them together is the point, not because anything requires it — an overload set is one declaration split across arms, and the arms may be written wherever they read best.
there is no formatter to run and no debate to have in review. a program has one canonical rendering, and non-canonical order is a syntax error the same way a stray brace would be. the payoff shows up when you split a real module by topic: alphabetical order inside each file stops being a constraint and becomes an index—you always know where a name will be before you look.
ensō-neko
types first, then the rest by name—one drawer per file, always sorted. split files by topic, the way a real library does, and the ordering inside each one stops being a rule you obey and starts being the table of contents you rely on.
private by default
look again at menu.kso: three of its four declarations are bare, and one wears pub. that single keyword is the entire visibility system. a top-level name is module-private unless marked—base_price is the module's own business, and nothing spells that out but its absence of ceremony. there is no export list to maintain and no visibility matrix to memorize. pub sits on the declaration, so every reader sees the surface exactly where the thing is defined: price and checkout are api; base_price, line, and service are interior.
pub sits on every kind of declaration—pub fn, pub type, and pub on a constant—and the boundary it draws is enforced at every import. an importer sees only pub names; reach for anything else and the compiler answers with error[opacity], naming the module whose interior you touched. the keyword is the whole contract, checked where the module's edge is crossed:
kanso check main.kso
dango costs 370
tests are constants
chapter 01 promised that tests are just test_ constants. drop a test file into the module. it shares the one namespace, so it can call the public price and reach straight past it to the private base_price—which is occasionally exactly what a test wants:
test_base_price_raw ... ok
test_dango_price ... ok
test_unknown_item_fails ... ok
3 passed, 0 failed
a test is a boolean constant. the runner evaluates each test_ name and reports. there is no assertion library, no matcher dsl, no lifecycle—test_dango_price is a name bound to (price "dango") == 400, and that is the whole framework. when a test fails, the report says only what the value was, because a failure is nothing more than a constant that came out false:
name a boolean test_something—that's the entire testing story. it scales further than it looks: the same three characters of ceremony pin a teahouse's prices and, in the next chapter, a full json parser's escapes, unicode, and error positions.
mugi
a test is a value, so everything you know about values applies: no setup, no teardown, no ordering, no shared state to leak between examples. a suite of constants cannot flake—there is nothing in it that time can touch. i have checked.
importing another module
one directory is one module; a program grows by putting modules side by side. an import names a sibling directory, and every pub name inside arrives under the module's own name as a qualifier—teahouse/price, spelled where it is used, so a reader never wonders where a name came from:
the boundary works in both directions. inside the teahouse, base_price is an ordinary name any file of the module may read; from outside, it does not exist, and asking for it is a compile error rather than a discouraged convention:
shady/peek_check.kso
import"teahouse"print"the wholesale number is {teahouse/base_price}"
kanso check peek_check.kso
error[opacity]: `base_price` is private to module `teahouse` — only pub names cross an import
--> peek_check.kso:3:33
3 | print "the wholesale number is {teahouse/base_price}"
^
bare spellings work too: an imported pub also joins the short-name overload space, so price "dango" dispatches alongside any local arms of the same name. the qualified spelling is the one this book prefers, because it reads as documentation. either way, what crosses the boundary is exactly the surface the module marked—nothing to configure, nothing to forget.
what you can do now
you can lay a program out as a directory of files that read like a table of contents, trusting one namespace to knit them together with no imports or exports between them. you know that types come first and everything else is alphabetical, enforced, so there is never a placement decision to make or relitigate. you can mark your public surface with pub and leave everything else private by omission. and you can test any of it—public or private—by naming a boolean test_ and running kanso test, with no framework underneath and nothing in a suite of constants that can flake. that is enough machinery to build a real library, which is exactly where the next chapter goes.
exercises
split the teahouse further. add a third file service.kso holding a pub fn total that takes a list of item names, prices each through price, and sums them—calling into menu.kso across the file boundary. add a test_ constant for a two-item order and run kanso test teahouse.
trigger the canonical-order error on purpose in three ways: two functions out of alphabetical order, a type placed below a fn, and main after a constant that sorts before it. read each error[formatting] and predict the fix before you make it.
write a test that must fail—assert something you know is wrong—and read the runner's report. then make it pass by fixing the assertion, not the code, and watch the count flip.
from a file outside the teahouse module, import it and try to read base_price. read the error[opacity] you get back. then mark base_price as pub, watch the error disappear, and decide whether you meant it—widening a surface is a one-word edit, which is exactly why the compiler makes the narrow default free.