Tzopilotl
Docs
GitHub

Tzopilotl by Example

22. Modules and Visibility

22.1 Import Syntax

-- Import entire module (qualified access)
import math;
println(math.square(5));    -- 25

-- Import with alias
import std.strings as strs;
strs.quotes("hi") println;  -- "hi"

-- Import all exports into current scope (wildcard)
import std.strings.*;
"7" padStart(3, "0") println;   -- 007, no qualifier needed

-- Import specific names
import std.strings.{glob, padStart};
"kick.wav" glob("*.wav") println;

-- Import with aliases
import std.strings.{padStart as padLeft};
"7" padLeft(3) println;

General-purpose standard-library modules live under the std.* namespace (std.strings, std.path, std.fs, std.result, std.test, std.json, std.message, std.thunk, std.futures). Math, string, and collection functions are built-ins — always available, no import needed. Audio-domain modules (synthdef, common_ugens, filters, dsp_math) keep flat names. See the Standard Library reference.

22.2 Dotted Paths

-- Dotted paths map to directories:
--   std.strings  →  std/strings.x
--   utils        →  utils.x

import std.strings;             -- loads std/strings.x
strings.quotes("hi") println;   -- qualified by last path component

22.3 File Resolution

Module Search Order

When resolving an import, the compiler searches in this order:

  1. Relative to the directory containing the importing file
  2. Each directory in the CLI -I path1:path2 argument
  3. Each directory in the TZPL_PATH environment variable (colon-separated)
  4. The auto-discovered standard library ($TZPL_HOME/modules if set, else the modules/ directory found next to the executable, else the source tree's lang/modules)

Run with include paths:

./tzpl -I lib:vendor main.x

Or set the environment variable:

export TZPL_PATH=/usr/local/lib/tzpl:/home/user/libs

22.4 Visibility and Exporting

-- By default, all top-level declarations are exported
fn add(a Int, b Int) Int = a + b;     -- exported
const PI = 3.14159;                       -- exported
struct Point { x Float, y Float }       -- exported

-- Two ways to make a declaration private (not exported):

-- 1. The private keyword
private fn helper(x Int) Int = x * 2;

-- 2. Underscore prefix
fn _internal(x Int) Int = x + 1;

Private names cannot be accessed by importers:

import utils.*;   _internal(5);    -- compile error
import utils;     utils.helper(5);  -- compile error

Imported names are NOT re-exported by default. If module A imports from module B, importers of A do not see B's names. Use export (section 21.5) to opt in to re-exporting.

22.5 Re-exporting Imports with export

export is a drop-in replacement for import: the grammar is identical, but every name pulled in by an export statement is also added to this module's export table. The two keywords can be mixed freely within a module.

-- Plain `import`: symbols are local to this module only.
import math_utils.*;

-- `export`: symbols are local AND visible to importers of this module.
export math_utils.*;

-- Re-export only selected names.
export math_utils.{square, cube};

-- Re-export with renaming.
export math_utils.{square as sq};

-- Re-export the module alias so importers can use math_utils.square(...).
export math_utils;

-- Re-export a dotted path or renamed alias.
export std.strings as strs;

A downstream module then sees the re-exported names as if they were declared locally in the re-exporter:

-- File: math_bridge.x
export math_utils.*;                 -- re-exports square, cube, ...
fn quadruple(x Int) Int = x * 4;   -- local (and exported by default)

-- File: main.x
import math_bridge.*;
5 square println;                    -- 25  (from math_utils, via math_bridge)
2 quadruple println;                 -- 8   (local to math_bridge)

Shadowing

A local declaration with the same name as an imported one always wins. The re-export is silently dropped so the local definition is the sole exported binding:

export math_utils.*;                 -- math_utils defines `square`
fn square(x Float) Float = x * x; -- local square shadows the import
-- Importers of this module see only the local `square`.

Chained access through a re-exported alias

Given export math_utils; in module A, the alias math_utils is only reachable via a wildcard or named import of A. Writing A.math_utils.square(5) through a whole-module import (import A;) is rejected:

import A;
A.math_utils.square(5);   -- compile error: use `import A.*` or `import A.{math_utils}`

22.6 Circular Import Detection

Circular imports are detected and reported as errors. If a.x imports b.x and b.x imports a.x, the compiler will report: "Circular import detected: a"