Modules
- Public/Private Members
- Submodules
- Parents, Children, Siblings
- Module Caching
- Circular Imports
- Aliasing
- Importing Members
- Import Roots
- Executing Module Directories
- Importing Files/Strings
A module is a Pyro file loaded as a library. Modules are loaded using the import
keyword.
Assume we have a file called math.pyro
containing math functions:
import math;
The imported module's name becomes a variable in the importing scope:
assert math::abs(-1) == 1;
Use the member access operator ::
to access a module's top-level members — i.e. variables, functions, and classes.
Public/Private Members
A module's top-level members (variables, functions, and classes) are private by default
— use the pub
keyword to make them public, e.g.
pub var number = 123; pub def func() { return "hello world!"; } pub class Object { var value; }
- Public members can be accessed from inside or outside the module.
- Private members can only be accessed from inside the module.
Submodules
Modules can contain submodules. Assume we have a directory called math
containing a file called trig.pyro
:
import math::trig; var value = trig::cos(1);
The top-level module math
can live alongside the directory that contains its submodules:
root/ |-- math.pyro |-- math/ |-- trig.pyro
Alternatively, it can live inside the math
directory in a file called self.pyro
:
root/ |-- math/ |-- self.pyro |-- trig.pyro
Finally, the top-level math
module can be empty and simply function as a container for its submodules:
root/ |-- math/ |-- trig.pyro
Submodules can contain submodules of their own — there's no hard limit to how deep the nesting can go.
Parents, Children, Siblings
Imagine we have the following module structure:
root/ |-- math/ |-- calc.pyro |-- self.pyro |-- trig.pyro
A parent module can import its child – e.g. in root/math/self.pyro
:
import math::trig;
A child module can import its parent – e.g. in root/math/calc.pyro
:
import math;
A child module can import its sibling – e.g. in root/math/calc.pyro
:
import math::trig;
Module Caching
A module file is only executed the first time it's imported. The imported module is cached and any subsequent imports simply load the module object from the cache.
Importing a submodule automatically executes and adds its ancestor modules to the cache if they haven't already been imported. For example, given the statement:
import foo::bar::baz;
-
The module
foo
will be executed and added to the cache if it hasn't already been imported. -
The module
foo::bar
will be executed and added to the cache if it hasn't already been imported. -
The module
foo::bar::baz
will be executed and added to the cache if it hasn't already been imported.
Only the final module name in the chain is declared as a variable in the importing scope — here baz
.
Circular Imports
In general, circular imports are fine — module foo
can import module bar
and module bar
can import module foo
.
Note that trying to use a member from an imported module that hasn't finished initializing can result in a panic.
Note that you can avoid the problems that sometimes result from top-level circular imports by importing the required module inside a function, e.g.
def do_some_math(value) { import math; return math::abs(value) * math::pi; }
This import will only fire when the function is run. (Imported modules are cached so the module won't be re-imported every time the function runs.)
Aliasing
You can import a module under an alias using an import ... as ...
statement:
import math as alias; var foo = alias::abs(-1);
Submodules can similarly be aliased:
import math::trig as alias; var foo = alias::cos(1);
Importing Members
You can import a top-level member from a module by wrapping its name in braces, e.g.
import math::{abs}; assert abs(-1) == 1;
You can alias the imported member using an import ... as ...
statement, e.g.
import math::{abs} as foo; assert foo(-1) == 1;
You can import multiple members in a single import
statement by separating their names with commas, e.g.
import math::{cos, sin};
You can alias the imported members using a comma-separated as
list, e.g.
import math::{cos, sin} as foo, bar;
You can import all the top-level members from a module using an asterisk, e.g.
import math::{*};
Note that you can only use this {*}
syntax at global scope in a script or module file — i.e. you can't import {*}
inside a block or function.
Import Roots
The global $roots
vector contains the list of root directories that Pyro checks when attempting to import a module.
-
When Pyro is executing a script it adds the parent directory containing the script to the
$roots
vector, along with amodules
directory within that parent directory. (This means that the modules required by a script can be located either in the same directory as the script or in amodules
directory alongside the script.) -
When Pyro is executing the REPL it adds the current working directory to the
$roots
vector.
To customize Pyro's import behaviour:
-
You can edit the
$roots
vector within a Pyro program to add or remove entries. -
You can use the
--import-root
command-line option to add additional root directories. -
You can use a
PYRO_IMPORT_ROOTS
environment variable to specify additional root directories. The value should be a list of:
separated directory paths.
Executing Module Directories
You can execute a module directory as a script if it contains a self.pyro
file:
$ pyro path/to/module/directory
Pyro executes the self.pyro
file, then the $main()
function if it finds one.
Importing Files/Strings
You can import any file as a module using the $exec()
function, which executes a string of Pyro source code as a new module, e.g.
var source_code = $read_file("path/to/file.pyro"); var module = $exec(source_code);
Unlike the import
statement, this doesn't add the new module to the cache.
You can also use the $exec()
function to create a new module directly from a string, e.g.
var module = $exec(`pub def func() { return "foobar"; }`); assert module::func() == "foobar";