Top > Design docs > Core design > Part 03
Sigmal does not have a distinct nominal module system.
Instead:
.) is the only mechanism for namespace access.All module behavior is defined in terms of records and projection.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
Re-exporting is ordinary record construction.
Example:
&{
add = .std.math.add;
}
Only explicitly included fields are exported.
There is no implicit re-export.
Sigmal defines no global namespace.
Only values reachable through:
exist in scope.
There is no global registry of modules, instances, or properties.
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)
In Sigmal:
Modules are fully integrated into the core language as ordinary values.
Sigmal.org - the calculus we can build on