Modules
prepoly organizes code into modules: every file is a module, and directories form the module path. Let’s split a small geometry library across files.
First, write geometry/vec.pp:
type Vec2 = { x: float64 y: float64}
fun Vec2.new(x, y) { return Self { x: x, y: y }}
fun Vec2.add(self, other) { return Self { x: self.x + other.x, y: self.y + other.y }}
fun Vec2.length(self) { return sqrt(self.x * self.x + self.y * self.y)}
fun dot(a, b) { return a.x * b.x + a.y * b.y}
fun _helper() { // A name starting with `_` is private to this module.}Then use it from main.pp, next to the geometry directory:
import geometry.vec.{ Vec2, dot }
fun main() { let a = Vec2.new(3.0, 4.0) let b = Vec2.new(1.0, 2.0) let c = a.add(b) println("a + b = ({c.x}, {c.y})") println("a . b = {dot(a, b)}") println("|a| = {a.length()}")}prepoly main.ppa + b = (4.0, 6.0)a . b = 11.0|a| = 5.0The import path follows the directory layout relative to the importing file:
geometry.vec is geometry/vec.pp. The braced list names what to import.
Two shorter forms cover the other common needs. A single name can skip the
braces, and importing the module itself makes its exports available
qualified by the path’s last segment. as overrides the qualifier:
import geometry.vec.dot // one name, same as .{ dot }import geometry.vec // whole module, used as vec.<name>import geometry.vec as g // same, but used as g.<name>
fun main() { let a = vec.Vec2.new(3.0, 4.0) let b = vec.Vec2 { x: 1.0, y: 2.0 } println("a . b = {vec.dot(a, b)}")}A few points worth noting:
- A type’s methods travel with it: importing
Vec2makesa.add(b)andVec2.new(...)available with no separate import. - A name beginning with
_(like_helper) is private to its module and cannot be imported. - The top-level standard library is an implicit prelude —
sqrt,println, and the array/string helpers need no import. Nested standard-library modules are not in the prelude and are imported explicitly, e.g.import std.collections.{ HashMap }.
The full rules are in the modules reference.