Authoritative source: Source files, names, and modules

Source files, names, and modules

A tulam source file normally has four layers: a module header, imports, an optional export policy, and declarations. Small single-file experiments may omit the header; project modules may not.

module Geometry.Point;

import Core;
import Math.Algebra (Field, zero);

export (Point, origin, translate);

type Point = x:Float64 * y:Float64;
value origin = Point(0.0, 0.0);
function translate(point:Point, dx:Float64, dy:Float64) : Point =
    Point(point.x + dx, point.y + dy);

Module identity

Module components begin with capital letters and are separated by dots. Within a project source root, the filename must agree with the module:

src/Geometry/Point.tl  →  module Geometry.Point;

The project planner rejects mismatches, duplicates, missing imports, and cycles. It builds environments for the complete reachable graph before type checking module bodies, so declarations do not depend on accidental file-loading order.

Imports and exports

Imports are explicit. Importing A does not automatically import everything that A imported.

import Math.Algebra;
import Math.Algebra (Field, zero);
import Math.Algebra hiding (unsafeApproximation);
open Math.Algebra;

The parenthesized form selects names; hiding excludes names. open brings all exported names into unqualified scope and is best used sparingly in public libraries. Qualified names remain available when unqualified names would be ambiguous.

An export list defines the public surface:

export (Point, origin, translate);

private prevents one declaration from being exported. opaque type exports a type identity while hiding its constructors, allowing clients to use the type only through its public operations.

Naming conventions

Identifiers may contain letters, digits, underscores, and apostrophes. A trailing # exists for legacy/compiler bindings; new APIs should use ordinary names and declared intrinsics.

Comments and documentation

// A line comment.

/* A block comment.
   Block comments do not nest. */

/// Documentation for the following declaration.
function square(x:Int) : Int = x * x;

Documentation comments belong immediately before the declaration they explain. Prefer describing contracts and invariants rather than repeating a name.

Reserved and contextual words

Keywords such as type, function, value, match, and class cannot be used as identifiers. Some words are contextual: on has special meaning in a declaration placement clause, and with after handle; elsewhere they may be ordinary names. resume is introduced only inside resumable handler clauses.

When uncertain, consult Appendix A or let tulam check identify the lexical conflict. Syntax highlighting is helpful, but the compiler is authoritative.

Separators

The rule is simple:

let { x = 1; y = 2; total = x + y } in total

Trailing semicolons are allowed in declaration blocks. Consistent separators make generated code and formatting predictable.

Current implementation

Module headers, filters, visibility, project path validation, and reachable graph planning are implemented. A future separate-compilation phase will add serialized typed interfaces and library artifacts; current project libraries are checked and consumed from source.

Common mistakes

Recap

A module has a stable dotted identity, explicit dependencies, and a deliberate public surface. These rules let projects reject ambiguous graphs before type checking begins.

Normative details: Language Reference §2–3.