Using libraries
Kio libraries are packages consumed through explicit dependency files. There is no ambient standard library or package registry: a consumer chooses a local name and a local-path or Git source, materializes the dependency under that name, and imports its public modules normally.
The reusable packages under test-data/poc/ are adopter-grade repository libraries. They are checked as real packages and are good starting points for applications, but their location does not make them compiler builtins.
The minimal dependency workflow
Create one <local>.dep.kio file at the consumer package root. For a nearby checkout, point path at the dependency's package file:
// elab.dep.kio
dependency elab;
source {
path "../libs/elab/elab.pkg.kio"
}
For a package inside a Git repository, provide a clone URL, a ref, and the package manifest's path relative to that repository's root:
// elab.dep.kio
dependency elab;
source {
git "https://github.com/jdevuyst/kio/";
ref "main";
path "test-data/poc/elab/workdir/elab.pkg.kio"
}
The filename stem, dependency header, and local import root are all elab. The dependency package's original name does not have to match it. The Git path selects that exact package file inside the checkout; a standalone source { path "..." } instead resolves from the consumer's package root. Git sources without an explicit path use package discovery.
From the consumer's package root, where elab.dep.kio lives, materialize the source:
kio dep fetch
For a Git source, the first fetch also writes elab.lock.kio with the resolved commit, selected path, and contract digest. An existing lock is honored; kio dep update is the explicit operation that moves the pin. Commit the dependency declaration, the Git lock when present, and the materialized elab/ module tree. The whole materialized closure is source, not a disposable build cache, so a fresh checkout can build without network access.
Re-rooted imports
Materialization places the dependency's module tree below the consumer-chosen local name. If the dependency declares module match;, a consumer that names it elab imports it as elab/match:
import elab/match(match);
import elab/spine_elaborators(widen_sum, one_prod);
The bang belongs only at the call site:
fn inject[A][B](value: A) -> A | B { widen_sum!(value, A | B) }
Importing a dependency does not automatically expose it through the consumer's host contract. If a bridged public signature reaches dependency types or host requirements, include the relevant re-rooted modules in the consumer's bridge block. Package files and bridges covers rehosting, retyping, contract closure, locks, and update compatibility in detail.
Collection and value libraries
Each package below has a root user-facing module and checked demos. Several use the host-provided loop capability for unbounded traversal because Kio' itself is strongly normalizing.
dictis an ordered persistent dictionary based on a red-black tree. Callers pass an explicit key comparator; ordering is a dependency, not inferred from a typeclass.listis a generic cons list with O(1) constructors and accessors plus loop-driven folds and transformations.optionrepresents optional values as the structural sumPresent(A) | ., with constructors, folds, mapping, binding, and collection helpers.queueis a persistent FIFO queue using the two-list representation, with amortized O(1) enqueue/dequeue behavior and explicit invariant helpers.resultis a right-biased structuralResult(T, E) = T | Ewith construction, folding, mapping, binding, recovery, and operator vocabulary.vecis a persistent indexed vector backed by a binary digit trie, with logarithmic lookup, update, and append paths.
With a local name matching the package name, typical imports are rooted at its public module:
import dict/dict(Dict, empty, insert);
import list/core(List, cons, nil);
import option/core(Option, none, some);
import queue/core(Queue, empty);
import queue/queue(enqueue);
import result/result(Result, ok, err);
import vec/vec(Vec, empty, push_back);
Select only the names the application uses. The package source and generated Kiodoc remain the authority for exact signatures.
All six collection and value packages above live in the same Git repository. For example, create list.dep.kio at your consumer package root:
dependency list;
source {
git "https://github.com/jdevuyst/kio/";
ref "main";
path "test-data/poc/list/workdir/list.pkg.kio"
}
For any other package in this table, name the file <local>.dep.kio, replace dependency list; with dependency <local>;, and replace only the path value with its row. The Git URL and ref stay the same; the import column shows the module root and one useful selection after materialization.
| Local name | Git path | Example import |
|---|---|---|
dict | test-data/poc/dict/workdir/dict.pkg.kio | import dict/dict(Dict, empty, insert); |
list | test-data/poc/list/workdir/list.pkg.kio | import list/core(List, cons, nil); |
option | test-data/poc/option/workdir/option.pkg.kio | import option/core(Option, none, some); |
queue | test-data/poc/queue/workdir/queue.pkg.kio | import queue/core(Queue, empty); |
result | test-data/poc/result/workdir/result.pkg.kio | import result/result(Result, ok, err); |
vec | test-data/poc/vec/workdir/vec.pkg.kio | import vec/vec(Vec, empty, push_back); |
Run kio dep fetch from the consumer package root after adding each dependency declaration. This writes its lock and materialized source under the chosen local name; import those modules as shown. A package's public host requirements still need the consumer's ordinary bridge/rehosting setup, as explained in Package files and bridges.
A list consumer with host bindings
The list/core constructors above use no host operations. Traversal functions in list/list, such as foldl, use the library's declared host capabilities. For example, a consumer can total two prices represented as integer cents. Use the list.dep.kio above and create invoice.pkg.kio:
package invoice;
bridge {
invoice;
list/core;
list/list;
list/elab/testapi
}
Then create invoice.kio:
module invoice;
import list/core as lists;
import list/list as traversal;
pub fn subtotal(first: traversal.I32, second: traversal.I32) -> traversal.I32 {
let prices = lists.cons(first, lists.cons(second, lists.nil()));
traversal.foldl(traversal.add_i32, 0, prices)
}
The bridge exposes the library's host requirements under list/list. Supply these bindings in the host implementation; the names below are relative to that module:
| Binding | Type or operation |
|---|---|
Bool, I32, String | Host types with roles bool, i32, and str |
add_i32, sub_i32 | (I32 & I32) -> I32 |
eq_i32, lt_i32 | (I32 & I32) -> Bool |
loop | [S][R] ((S -> S | R) & S) -> R; continue on the left arm, return the right arm |
string_concat | (String & String) -> String |
The elaborator dependency also requires host types under list/elab/testapi: Bool (bool), I32 and Int (both i32), and String (str).
These are the bridged modules' requirements even though this particular function only uses addition and iteration. With those bindings, calling subtotal(125, 250) returns 375. The host integration guides show how to supply typed host values and functions in each language. To reuse bindings already provided by a consumer module, put rehost list/list to your_module; in list.dep.kio and bridge that provider; rehosting explains the compatible-name and type requirements.
Fetch and check from the directory containing invoice.pkg.kio:
kio dep fetch
kio check
kio test
The elaborator library
elab is the reusable compile-time structural toolkit used throughout these guides. Its modules are deliberately separate so a consumer can import a narrow vocabulary:
spine_elaboratorssupplies the everydayfit!,reorder_*,narrow_*,widen_*,flatten_*, andone_*product/sum adapters.row_elaboratorsconstructs, updates, and projects label-product rows.tuple_elaboratorssupplies tuple operations such ashead!,tail!,concat!,flatten!,group!,zip!, andmap!.matchsupplies exhaustive, first-match structural dispatch throughmatch!.controlsupplies lazyif!branches and ordinaryscope!blocks.sequencesuppliesBindandSequencetypes anddo!blocks that sequence actions through an explicitly supplied bind function.lookupsupplies the advanced conditional cross-sum helperslookup!andcontains!. They inspect whether an active sum branch contains a requested label; they are not aliases for.?{field}, which projects a statically known product field.derivecomposes an explicit set of candidate rules to build one target value throughderive!.type_ofreflects a requested type into the library's user-level type representation.showsynthesizes a string renderer for supported structural shapes.algebraic_elaboratorssupplies the DNF-leveliso!,into!,onto!,align!,ease!, andatom!palette.elaborator_utilcontains shared implementation types and folds for elaborator authors rather than an application-facing surface.
For example:
import elab/match(match);
import elab/control(if, scope);
import elab/sequence(Bind, Sequence, do);
import elab/lookup(contains, lookup);
import elab/spine_elaborators(fit, widen_sum);
Defining elaborators explains the reflection ABI behind these modules. The elaborator library case study walks their implementations and laws.
The optics library
optics provides lenses, prisms, and isomorphisms as pure function pairs. It includes composition, view, set, over, prism preview/review, product lenses, sum prisms, and structural isomorphisms. It depends on elab for the spine adapters, so consuming its materialized package also consumes the committed dependency closure.
Import the public module through the chosen root:
import optics/optics(Lens, compose_lens, view, set);
See The optics library for the complete executable tour and its equiv laws.
Case studies, not drop-in libraries
Two POCs teach integration patterns rather than offering a single reusable module surface:
- Higher-kinded types assembles kinded brands, explicit
Functor/Monaddictionaries, monadicdo, andderive!. Use it as a design case study; the focused language mechanics are in Higher-kinded types. - Dynamic loading demonstrates a host loading and contract-matching an emitted Kio' package. It is an end-to-end loader and guest scenario, not a general module to import; start with Dynamic loading.
Verifying an adopted package
Run the consumer's normal checks after materialization:
kio check
kio test
kio test skips dependency-owned equiv declarations by default. Add --include-deps when you intentionally want to discharge the full adopted closure as well. The materialized tree should stay canonical: if the declared source changes, regenerate it with kio dep fetch (or deliberately re-pin a Git dependency with kio dep update) and commit the resulting source changes.