Library Authoring Guide¶
This document covers how to create, organize, and use Scheme libraries with Kaappi's R7RS library system.
define-library Syntax¶
A library definition contains a name and one or more declarations:
(define-library (library-name ...)
(export export-spec ...)
(import import-set ...)
(begin body ...)
(include filename ...)
(include-ci filename ...)
(cond-expand clause ...))
Declarations¶
| Declaration | Purpose |
|---|---|
export |
Names to make visible to importers |
import |
Libraries this library depends on |
begin |
Scheme code defining the library's bindings |
include |
Include source from another file (as if pasted into begin) |
include-ci |
Like include, but case-folded identifiers |
cond-expand |
Conditional declarations based on feature flags |
Complete Example¶
A math utility library in mylib/math.sld:
(define-library (mylib math)
(export square cube factorial fibonacci)
(import (scheme base))
(begin
(define (square x) (* x x))
(define (cube x) (* x x x))
(define (factorial n)
(let loop ((i n) (acc 1))
(if (= i 0) acc
(loop (- i 1) (* i acc)))))
(define (fibonacci n)
(let loop ((i 0) (a 0) (b 1))
(if (= i n) a
(loop (+ i 1) b (+ a b)))))))
Using it from a program:
(import (mylib math))
(display (square 5)) ;=> 25
(newline)
(display (factorial 10)) ;=> 3628800
(newline)
(display (fibonacci 20)) ;=> 6765
(newline)
Export Specifications¶
The export declaration lists what the library makes available:
;; Export names as-is
(export square cube factorial)
;; Export with renaming
(export (rename internal-name external-name))
Example with renaming:
(define-library (mylib strings)
(export (rename str-join join)
(rename str-split split))
(import (scheme base))
(begin
(define (str-join lst sep) ...)
(define (str-split s sep) ...)))
Import Sets and Modifiers¶
The import declaration accepts the same import sets as program-level
import: plain library names plus the only, except, rename, and
prefix modifiers, which can be nested. See
Import Modifiers in the Libraries guide.
File Naming and Search Paths¶
Naming Convention¶
Library names map to file paths by joining components with / and appending
.sld:
| Library Name | File Path |
|---|---|
(mylib math) |
mylib/math.sld |
(mylib util strings) |
mylib/util/strings.sld |
(srfi 1) |
srfi/1.sld |
Search Order¶
Kaappi searches for .sld files in this order:
- Current directory (
./) ./lib/subdirectory- Directories specified with
--lib-path
Example:
With this invocation, (import (mylib math)) searches:
./mylib/math.sld./lib/mylib/math.sld/opt/scheme-libs/mylib/math.sld./vendor/mylib/math.sld
Bytecode Caching¶
Programs and libraries are both cached automatically. Running
kaappi program.scm stores the compiled bytecode in a central cache at
$KAAPPI_HOME/cache (default ~/.kaappi/cache), keyed by the source
content and the exact binary that compiled it -- so an upgraded or rebuilt
kaappi never serves stale bytecode. Subsequent runs of an unchanged
program skip the read/expand/compile pipeline entirely.
A .sld library loaded from a file gets its own cache entry (since
v0.25.0). The design is "structure from source, code from cache": on every
load the .sld is still read and its declarations walked normally --
imports load, exports are re-derived, cond-expand re-selects,
define-record-type creates its types -- but the body's compiled
procedures and its macro transformers are replayed from the cache instead
of being recompiled. Invalidation follows the files you edit: changing a
library, a file it includes, or any library it imports invalidates its
entry and every entry that transitively imported it (a program's cached
code embeds the expansions of macros it imported, so a program entry goes
stale on a library edit too), and so does a --lib-path change that
resolves a dependency somewhere else.
Two things are deliberately never cached: a library whose cond-expand
tests library availability (a (library ...) requirement or an srfi-N
feature), because that answer depends on the live library path rather than
on anything a cache key can hash; and a program whose own top level
defines a macro with define-syntax (macros inside a library are fine --
the library's entry stores the transformers themselves). kaappi --timings
names the reason whenever a file is not cached.
Nothing is ever written next to your sources. (An earlier scheme that read
.sbc files placed beside libraries was removed as unsound -- if you have
stray .sbc files sitting beside your .sld sources from following older
docs, they are ignored and can be deleted.) Inspect or wipe the cache from
the CLI:
kaappi cache status # cache location, entries, sizes, staleness
kaappi cache clear # remove all entries
kaappi --compile file.scm still writes an explicit .sbc artifact -- an
output you asked for by name (for example to embed with zig build
-Dbundle=file.sbc), separate from the automatic cache.
Available Libraries¶
For everything you can import — the 16 standard R7RS libraries, all 181 SRFIs, and the Kaappi extension libraries — see Available Libraries and SRFI Support.
cond-expand for Portable Code¶
Use cond-expand to write code that adapts to different Scheme implementations:
(define-library (mylib compat)
(export platform-name)
(import (scheme base))
(cond-expand
(kaappi
(begin
(define platform-name "kaappi")))
(chicken
(begin
(define platform-name "chicken")))
(else
(begin
(define platform-name "unknown")))))
The features procedure returns the list of feature identifiers that Kaappi
supports:
Library Organization Tips¶
- Put each library in its own
.sldfile. - Use a consistent directory structure that mirrors library names.
- Keep a
lib/directory for project-local libraries. - Use
includeto split large libraries across multiple files:
(define-library (mylib big)
(export ...)
(import (scheme base))
(include "big-part1.scm")
(include "big-part2.scm"))
- Keep formatting canonical with the built-in formatter (2-space R7RS indentation, comment-preserving, guarded so it never changes meaning):
Next: SRFI Support