Zeta extends Epsilon (the version with statically scoped local abstracts) by adding modules.
Informally, a module is a named collection of declarations. A module may contain declarations of variables, constants, functions, procedures, even sub-modules. Familiar examples of modules include packages, classes, objects, and abstract data types (ADTs).
The Modularity Principle states:
Programs should be constructed from cohesive, loosely coupled modules.
A module is cohesive if the declarations it contains are closely related.
A collection of modules is loosely coupled if the export-import relationships between them are minimized. Where
m1 exports from m2
or equivalently:
m2 imports to m1
means that m1 uses some of the declarations contained in m2.
For example, assume m2 contains a declaration of the constant pi:
[const pi 3.1416]
Assume m1 contains the definition of a function for computing the area of a circle:
[const area (fun (rad) (* rad rad m2.pi))]
In this case m1 imports the declaration of pi from m2. Note that m1 must qualify imported names with the name of the source module.
m2.pi
This could be useful if, for example, m1 contained a less accurate declaration of pi:
[const pi 3]
but wanted to use m2's more accurate version in the declaration of its area function.
-> [const mathUtils
(module
[const square (fun (x) (* x x))]
[const cube (fun (x) (* x (square
x)))]
[const abs (fun (x) (if (< x
0) (- 0 x) x))])]
done
-> (mathUtils.cube (mathUtils.abs -4))
64
-> [const moreUtils
(extends mathUtils
[const double (fun (x) (+ x x))]
[const triple (fun (x) (+ x
(double x)))])]
done
-> (moreUtils.triple 6)
18
-> (moreUtils.square 6)
36
Here's an environment diuagram showing moreUtils:

-> [const program1 (module
[var more true]
[var input 0]
[const main (proc ()
{while more
{begin
{read input}
{if (= input -1)
{assign more false}
{display (mathUtils.square x)}}}}]]
done
-> [const point1 (module [var xc 3] [var yc 4])]
done
-> (+ (mathUtils.square point1.xc) (mathUtils.square point2.xc))
25
-> [const account1
(module
[var balance 0]
[const deposit
(proc (amt) { assign balance
(+ balance amt) })]
[const withdraw
(proc (amt)
{ if (<= amt balance)
{ assign balance (-
balance amt) } })])]
done
-> {account1.deposit 50}
ok
-> {account1.withdraw 20}
of
-> account1.balance
30
-> [const account2
(let [[ balance 0]]
(module
[var balance 0]
[const getBalance (fun ()
balance)]
[const deposit
(proc (amt) { assign
balance (+ balance amt) })]
[const withdraw
(proc (amt)
{ if (<= amt balance)
{ assign balance (-
balance amt) } })]))]
done
-> {account2.deposit 50}
ok
-> {account2.withdraw 30}
of
-> account2.balance
error, undefined symbol: account2.balance
-> (account2.getBalance)
20
Here's an environment diagram showing the account2 object:

-> [const makeAccount
(fun (initBalance)
(let [[ balance initBalance]]
(module
[var balance 0]
[const
getBalance (fun () balance)]
[const deposit
(proc (amt) { assign
balance (+ balance amt) })]
[const withdraw
(proc (amt)
{ if (<= amt
balance)
{ assign balance
(- balance amt) } })])))]
done
-> [const account3 (makeAccount 0)]
done
-> [const account4 (makeAccount 200)]
done
-> {account3.deposit 10}
ok
-> {account4.withdraw 55}
ok
-> (+ (account3.getBalance) (account4.getBalance))
155
Zeta adds module constructors and extenders to the expressions of Epsilon:
<Expression> ::= <ModuleConstructor> |
<ModuleExtender> | etc.
<ModuleConstructor> ::= (module <Declaration>+)
<ModuleExtender> ::= (extends <Symbol> <Declaration>+)
In addition, Zeta adds qualified symbols to Epsilon:
<Symbol> ::= <QualifiedSymbol> | <UnqualifiedSymbol>
Epsilon style symbols are called unqualified symbols in Zeta:
<UnqualifiedSymbol> ::= <Letter>(<Letter> | <Digit>)*
A qualified symbol is a sequence of unqualified symbols separated by periods:
<QualifiedSymbol> ::= <UnqualifiedSymbol>(.<UnqualifiedSymbol>)*
Here are some examples of qualified symbols:
account1.balance
moreUtils.square
computer.cpu.alu.adder.gate.transistor
Module constructors and extenders are expressions. Like all expressions, when executed, they produce values. The values produced by these expressions are called modules.
A module contains the bindings produced by executing the declarations that appear in the module constructor. For example, executing the module constructor:
(module [const a 42] [const b false])
produces the module:

The frames used by Epsilon can be extended to represent modules:
class Module extends Frame {
Module extension; // where this module inherits from
Frame definingEnv; // for the static
scope rule
// etc.
}