sigmal-docs

Sigmal Language Specification — Part 03

Modules, Namespaces, and Module Semantics

Top > Design docs > Core design > Part 03


Sigmal does not have a distinct nominal module system.

Instead:

All module behavior is defined in terms of records and projection.


1. Compilation Units

A source code unit is a compilation unit (it need not correspond to a filesystem file).

A compilation unit consists of:

A compilation unit is implicitly a block expression evaluated in order. Top-level definitions are bindings within this block.

The value of the compilation unit is the value of its final expression.

Example:

helper =
  \ (x : Nat) -> Nat = + x 1;

&{
  public = helper;
}

The module’s value is the final record.

If the final expression is not a record, the compilation unit still evaluates to a value, but it is not usable as a namespace via projection.

There is no implicit record wrapping of top-level bindings; only the final expression determines the exported value.

If the compilation unit ends with a trailing ;, its final value is Unit.


2. Exports

There is no separate export declaration mechanism.

The exported interface of a compilation unit is determined entirely by its final value.

If the final value is a record:

If the final value is not a record:

Bindings that appear before the final expression are local unless included in the final value.

Example:

private =
  \ (x : Nat) -> Nat = x;

&{
  public = private;
}

Only public is exported.


3. Modules as Records

A module usable as a namespace must evaluate to a record value.

Record types used for module namespaces are non-dependent, consistent with the core record rules defined in Part 02.

Example:

&{
  add =
    \ (x : Nat)
      (y : Nat)
    -> Nat
    = + x y;
}

After importing:

math = .math;
math.add 1 2

Projection works because math is a record.


4. Non-Record Compilation Units

A compilation unit may evaluate to any value.

Example (function-valued unit):

\ (x : Nat) -> Nat = + x 1

After import:

inc = .inc;
inc 41

Attempting:

inc.foo

is a type error, since inc is not a record.

Example (type-valued unit):

Nat

After import:

MyNat = .MyNat;

MyNat is a value of a universe.


5. Projection

The operator . is record projection.

Dot-starting projections are resolved by the resolver as projections from the distinguished top-level module record.

For any record value r:

r.k

is sugar for:

r.@{ TextSym "k" }

The injected symbol must be const-normalizable, as required by the core const-normalization rules.

Projection is left-associative:

a.b.c

means:

(a.b).c

There is no alternate meaning of . in import statements.


6. Symbol-Keyed Projection

Projection keys are symbols.

Identifiers in projection position are sugar for symbol injection.

r.@{ TextSym "k" }

Explicit symbol injection is allowed:

r.@{ someConstSym }

where someConstSym must be const-normalizable.

Projection keys must be const-normalizable. Runtime Sym values cannot determine projection keys, and there is no reflection from runtime values into namespace structure.


7. Import and Dot-Starting Projection

Import introduces a local binding to the value of another compilation unit.

Surface form:

alloc = .std.alloc;

Dot-starting projections (expressions beginning with .) are resolver-anchored projections.

They are allowed in any expression position where a projection expression is valid.

A dot-starting projection:

.std.alloc

is interpreted by the resolver as projection from the distinguished top-level module record.

Import is just a top-level binding whose right-hand side is such a projection.

There is no special “import context”; dot-starting projection is an ordinary expression form whose root is supplied externally by the resolver.


8. The Top-Level Module Value

Compilation occurs relative to a distinguished top-level record value supplied by the resolver.

Dot-starting projection expressions are resolved relative to a distinguished top-level module record supplied by the resolver.

The core language does not define how this record is constructed, only that it is a record value and obeys all core typing and normalization rules.

Example:

.std.alloc

means:

Project the field std from the top-level module record, then project alloc.

The core language does not define how this record is constructed.

The resolver-provided top-level record is an ordinary record value and obeys all core typing and normalization rules.


9. Resolver Independence

Module resolution is external to the core language.

The resolver provides:

Possible resolver strategies include:

For a fixed resolver configuration and target triple, resolution must be deterministic.

The core type system makes no assumptions about storage or linking mechanisms.

The resolver-provided top-level record includes standardized .std.* modules (see Part 01).


10. Structural Equality of Modules

Modules are record values.

Two modules are definitionally equal if:

Definitional equality here is the core structural equality defined in Part 02 and does not perform symbolic rewriting beyond const-normalizable expressions.

There is no nominal module identity.


11. Re-Exporting

Re-exporting is ordinary record construction.

Example:

&{
  add = .std.math.add;
}

Only explicitly included fields are exported.

There is no implicit re-export.


12. No Global Namespace

Sigmal defines no global namespace.

Only values reachable through:

exist in scope.

There is no global registry of modules, instances, or properties.


13. Interaction with Const and Symbols

Symbol injection may be used in projection:

.@{ TextSym "std" }.@{ someConstSym }

Each injected symbol must be const-normalizable.

Projection is always structural record access.

There is no dynamic path resolution at runtime.

NOTE: TextSym (and related symbol construction utilities) are provided by .std.core. (See Part 04)


14. Summary

In Sigmal:

Modules are fully integrated into the core language as ordinary values.


Sigmal.org - the calculus we can build on